ためすう

継承による委譲の置き換え (リファクタリング-p355)

2019-03-20

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「継承による委譲の置き換え」について書きます。

「継承による委譲の置き換え」 について

データの共有は継承では変換できない責任です。

変更前

<?php

class Employee
{
    /* @var Person */
    private $_person;

    public function __construct()
    {
        $this->_person = new Person();
    }

    public function getName()
    {
        return $this->_person->getName();
    }

    public function setName($arg)
    {
        $this->_person->setName($arg);
    }

    public function toString()
    {
        return 'Emp: ' . $this->_person->getLastName();
    }
}

class Person
{
    private $_name;

    public function getName()
    {
        return $this->_name;
    }

    public function setName($arg)
    {
        $this->_name = $arg;
    }

    public function getLastName()
    {
        return 'getLastName';
    }
}

$e = new Employee();
$e->setName('namename');
echo $e->getName() . "\n";
echo $e->toString() . "\n";

変更後

<?php

class Employee2 extends Person2
{
    public function toString()
    {
        return 'Emp: ' . $this->getLastName();
    }
}

class Person2
{
    private $_name;

    public function getName()
    {
        return $this->_name;
    }

    public function setName($arg)
    {
        $this->_name = $arg;
    }

    public function getLastName()
    {
        return 'getLastName';
    }
}

$e = new Employee2();
$e->setName('namename');
echo $e->getName() . "\n";
echo $e->toString() . "\n";

インターフェースの抽出 (リファクタリング-p341)

2019-03-19

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「インターフェースの抽出」について書きます。

「インターフェースの抽出」 について

複数のクライアントが、あるクラスのひとまとまりのインターフェースを使っている。または2つのクラス間でインターフェースの一部が共通である。

クラスは、互いにさまざまな方法で利用し合います。あるクラスを利用することは、しばしばそのクラスのすべての責任を当てにすることを意味します。

変更前

<?php

class Timesheet
{
    public function charge(Employee $emp, $days)
    {
        $base = $emp->getRate() * $days;

        if ($emp->hasSpeciallSkill()) {
            return $base * 1.05;
        }

        return $base;
    }
}

class Employee
{
    public function getRate()
    {
        return 0.72;
    }

    public function hasSpeciallSkill()
    {
        return true;
    }
}

$e = new Employee();
$ts = new TimeSheet();
echo $ts->charge($e, 200) . "\n";

変更後

<?php

class Timesheet2
{
    public function charge(Billable2 $emp, $days)
    {
        $base = $emp->getRate() * $days;

        if ($emp->hasSpeciallSkill()) {
            return $base * 1.05;
        }

        return $base;
    }
}

class Employee2 implements Billable2
{
    public function getRate()
    {
        return 0.72;
    }

    public function hasSpeciallSkill()
    {
        return true;
    }
}

interface Billable2
{
    public function getRate();
    public function hasSpeciallSkill();
}

$e = new Employee2();
$ts = new TimeSheet2();
echo $ts->charge($e, 200) . "\n";

スーパークラスの抽出 (リファクタリング-p336)

2019-03-18

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「スーパークラスの抽出」について書きます。

「スーパークラスの抽出」 について

似通った特性を持つ2つのクラスがある。

コードの重複は、システムにおける主要な問題点の1つです。

変更前

<?php

class Employee
{
    private $_name;
    private $_annualCost;
    private $_id;

    public function __construct($name, $id, $annualCost)
    {
        $this->_name = $name;
        $this->_id = $id;
        $this->_annualCost = $annualCost;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function getName()
    {
        return $this->_name;
    }

    public function getAnnualCost()
    {
        return $this->_annualCost;
    }
}

class Department
{
    private $_name;
    private $_staff = [];

    public function __construct($name)
    {
        $this->_name = $name;
    }

    public function getTotalAnnualCost()
    {
        $e = $this->getStaff();
        $result = 0;
        foreach ($e as $v) {
            $result += $v->getAnnualCost();
        }

        return $result;
    }

    public function getHeadCount()
    {
        return count($this->_staff);
    }

    public function addStaff(Employee $arg)
    {
        $this->_staff[] = $arg;
    }

    public function getStaff()
    {
        return $this->_staff;
    }

    public function getName()
    {
        return $this->_name;
    }
}

$e1 = new Employee('emp1', 100, 2000);
$e2 = new Employee('emp2', 200, 5000);

echo $e1->getName() . "\n";
echo $e2->getName() . "\n";

$d = new Department('department1');
$d->addStaff($e1);
$d->addStaff($e2);

echo $d->getName() . "\n";
echo $d->getTotalAnnualCost() . "\n";

変更後

<?php

abstract class Party
{
    private $_name;

    protected function __construct($name)
    {
        $this->_name = $name;
    }

    public function getName()
    {
        return $this->_name;
    }

    abstract public function getAnnualCost();
}

class Employee2 extends Party
{
    private $_annualCost;
    private $_id;

    public function __construct($name, $id, $annualCost)
    {
        parent::__construct($name);
        $this->_id = $id;
        $this->_annualCost = $annualCost;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function getAnnualCost()
    {
        return $this->_annualCost;
    }
}

class Department2 extends Party
{
    private $_staff = [];

    public function __construct($name)
    {
        parent::__construct($name);
    }

    public function getAnnualCost()
    {
        $e = $this->getStaff();
        $result = 0;
        foreach ($e as $v) {
            $result += $v->getAnnualCost();
        }

        return $result;
    }

    public function getHeadCount()
    {
        return count($this->_staff);
    }

    public function addStaff(Employee2 $arg)
    {
        $this->_staff[] = $arg;
    }

    public function getStaff()
    {
        return $this->_staff;
    }
}

$e1 = new Employee2('emp1', 100, 2000);
$e2 = new Employee2('emp2', 200, 5000);

echo $e1->getName() . "\n";
echo $e2->getName() . "\n";

$d = new Department2('department1');
$d->addStaff($e1);
$d->addStaff($e2);

echo $d->getName() . "\n";
echo $d->getAnnualCost() . "\n";

サブクラスの抽出 (リファクタリング-p330)

2019-03-18

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「サブクラスの抽出」について書きます。

「サブクラスの抽出」 について

「サブクラスの抽出」を行う主なきっかけは、あるクラスが、特定のインスタンスだけでしか使われない振る舞いを持っていることがわかった場合です。

変更前

<?php

class JobItem
{
    private $_unitPrice;
    private $_quantity;
    private $_isLabor;
    private $_employee;


    public function __construct($unitPrice, $quantity, $isLabor, Employee $employee)
    {
        $this->_unitPrice = $unitPrice;
        $this->_quantity = $quantity;
        $this->_isLabor = $isLabor;
        $this->_employee = $employee;
    }

    public function getTotalPrice()
    {
        return $this->getUnitPrice() * $this->_quantity;
    }

    public function getUnitPrice()
    {
        return $this->_isLabor ?
            $this->_employee->getRate() :
            $this->_unitPrice;
    }

    public function getQuantity()
    {
        return $this->_quantity;
    }

    public function getEmployee()
    {
        return $this->_employee;
    }
}

class Employee
{
    private $_rate;

    public function __construct($rate)
    {
        $this->_rate = $rate;
    }

    public function getRate()
    {
        return $this->_rate;
    }
}

$e = new Employee(0.6);
$ji = new JobItem(2, 100, true, $e);
echo $ji->getTotalPrice() . "\n";

変更後

<?php

class JobItem2
{
    private $_unitPrice;
    private $_quantity;
    private $_isLabor;
    private $_employee;


    protected function __construct($unitPrice, $quantity, $isLabor, Employee2 $employee)
    {
        $this->_unitPrice = $unitPrice;
        $this->_quantity = $quantity;
        $this->_isLabor = $isLabor;
        $this->_employee = $employee;
    }

    public function getTotalPrice()
    {
        return $this->getUnitPrice() * $this->_quantity;
    }

    public function getUnitPrice()
    {
        return $this->_isLabor ?
            $this->_employee->getRate() :
            $this->_unitPrice;
    }

    public function getQuantity()
    {
        return $this->_quantity;
    }

    public function getEmployee()
    {
        return $this->_employee;
    }
}

class LaborItem2 extends JobItem2
{
    public function __construct($unitPrice, $quantity, $isLabor, Employee2 $employee)
    {
        parent::__construct($unitPrice, $quantity, $isLabor, $employee);
    }
}

class Employee2
{
    private $_rate;

    public function __construct($rate)
    {
        $this->_rate = $rate;
    }

    public function getRate()
    {
        return $this->_rate;
    }
}

$e = new Employee2(0.6);
$ji = new LaborItem2(2, 100, true, $e);
echo $ji->getTotalPrice() . "\n";

コンストラクタ本体の引き上げ (リファクタリング-p325)

2019-03-15

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「コンストラクタ本体の引き上げ」について書きます。

「コンストラクタ本体の引き上げ」 について

複数のサブクラスに内容がほとんど同一のコンストラクタがある。

しかしコンストラクタでは、この共通の振る舞いがしばしば生成処理なのです。

変更前

<?php

class Employee
{
    protected $_name;
    protected $_id;
}

class Manager extends Employee
{
    private $_grade;

    public function __construct($name, $id, $grade)
    {
        $this->_name = $name;
        $this->_id = $id;
        $this->_grade = $grade;
    }

    public function getGrade()
    {
        return $this->_grade;
    }
}
$m = new Manager('name', 456, 20);
echo $m->getGrade() . "\n";

変更後

<?php

class Employee2
{
    protected $_name;
    protected $_id;

    protected function __construct($name, $id)
    {
        $this->_name = $name;
        $this->_id = $id;
    }
}

class Manager2 extends Employee2
{
    private $_grade;

    public function __construct($name, $id, $grade)
    {
        parent::__construct($name, $id);
        $this->_grade = $grade;
    }

    public function getGrade()
    {
        return $this->_grade;
    }
}
$m = new Manager2('name', 456, 20);
echo $m->getGrade() . "\n";

条件判定による例外の置き換え (リファクタリング-p315)

2019-03-13

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「条件判定による例外の置き換え」について書きます。

「条件判定による例外の置き換え」 について

例外を発生させているが、本来は呼び出し側が先にチェックすべきである。

例外は、例外的な振る舞い、すなわち予期しないエラーに対して使用すべきです。

条件判定の代わりとして使うべきではありません。

変更前

<?php

class ResourcePool
{
    private $_available = [];
    private $_allocated = [];

    public function getResource()
    {
        $result = [];
        try {
            // null が返却されてもエラーにならない
            $result = array_pop($this->_available);
            array_push($this->_allocated, $result);

            return $result;
        } catch (Exception $ex) {
            array_push($this->_allocated, 'resource');
            return $result;
        }
    }
}

$rp = new ResourcePool();
var_dump($rp->getResource());

変更後 (その1)

<?php

class ResourcePool2
{
    private $_available = [];
    private $_allocated = [];

    public function getResource()
    {
        $result = [];
        if (empty($this->_available)) {
            $result = ['resource'];
            array_push($this->_allocated, $result);

            return $result;
        }

        try {
            $result = array_pop($this->_available);
            array_push($this->_allocated, $result);

            return $result;
        } catch (Exception $ex) {
            array_push($this->_allocated, 'resource');
            return $result;
        }
    }
}

$rp2 = new ResourcePool2();
var_dump($rp2->getResource());

変更後 (その2)

<?php

class ResourcePool3
{
    private $_available = [];
    private $_allocated = [];

    public function getResource()
    {
        $result = [];
        if (empty($this->_available)) {
            $result = ['resource'];
        } else {
            $result = array_pop($this->_available);
        }

        array_push($this->_allocated, $result);

        return $result;
    }
}

$rp3 = new ResourcePool3();
var_dump($rp3->getResource());

例外によるエラーコードの置き換え (リファクタリング-p310)

2019-03-11

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「例外によるエラーコードの置き換え」について書きます。

「例外によるエラーコードの置き換え」 について

メソッドがエラーを示す特別なコードをリターンしている。

代わりに例外を発生させる

変更前

<?php

class Account
{
    private $_balance;

    public function __construct($balance)
    {
        $this->_balance = $balance;
    }

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            return -1;
        } else {
            $this->_balance -= $amount;
            return 0;
        }
    }
}

$a1 = new Account(1000);
echo $a1->withdraw(500) . "\n";
echo $a1->withdraw(1500) . "\n";

変更後 (チェックされない例外)

<?php

// 呼び出し側でチェック
class Account2
{
    private $_balance;

    public function __construct($balance)
    {
        $this->_balance = $balance;
    }

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            throw new Exception('出金額が多すぎる');
        }

        $this->_balance -= $amount;
    }

    public function getBalance()
    {
        return $this->_balance;
    }
}

$a2 = new Account2(1000);
$a2->withdraw(500);
echo $a2->getBalance() . "\n";

// 例外発生
// $a2->withdraw(1500);

変更後 (チェックされる例外)

<?php

class BalanceException extends Exception {}

class Account3
{
    private $_balance;

    public function __construct($balance)
    {
        $this->_balance = $balance;
    }

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            throw new BalanceException();
        }

        $this->_balance -= $amount;
    }

    public function getBalance()
    {
        return $this->_balance;
    }
}

$a3 = new Account3(1000);
$a3->withdraw(300);
echo $a3->getBalance() . "\n";

try {
    $a3->withdraw(800);

    // doTheUsualThin();
} catch (BalanceException $ex) {
    echo '例外発生: BalanceException' . "\n";
    // handleOverdrawn();
}

FactoryMethod によるコンストラクタの置き換え (リファクタリング-p304)

2019-03-10

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「FactoryMethod によるコンストラクタの置き換え」について書きます。

「FactoryMethod によるコンストラクタの置き換え」 について

オブジェクトを生成する際に、単純な生成以上のことをしたい。

変更前

<?php

class Employee
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    private $_type;

    public function __construct($type)
    {
        $this->_type = $type;
    }

    public function getType()
    {
        return $this->_type;
    }
}

$e1 = new Employee(1);
echo $e1->getType() . "\n";

変更後

<?php

class Employee2
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    public static function create($type)
    {
        switch ($type) {
            case self::ENGINEER:
                return new Engineer();
            case self::SALESMAN:
                return new Salesman();
            case self::MANAGER:
                return new Manager();
        }

        throw new Exception('不正なタイプコード');
    }
}

class Salesman extends Employee2
{
    public function __consturct() {}
}

$e2 = Employee2::create(1);
echo get_class($e2) . "\n";

setメソッドの削除 (リファクタリング-p300)

2019-03-09

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「setメソッドの削除」について書きます。

「setメソッドの削除」 について

setメソッドを提供するということは、そのフィールドが変更される可能性を示しています。

変更前

<?php

class Account
{
    private $_id;

    public function __construct($id)
    {
        $this->setId($id);
    }

    public function setId($arg)
    {
        $this->_id = 'ZZ' . $arg;
    }

    public function getId()
    {
        return $this->_id;
    }
}
$a = new Account(10);
echo $a->getId() . "\n";

変更後

<?php

class Account2
{
    private $_id;

    public function __construct($id)
    {
        $this->initializeId($id);
    }

    private function initializeId($arg)
    {
        $this->_id = 'ZZ' . $arg;
    }

    public function getId()
    {
        return $this->_id;
    }
}
$a = new Account(10);
echo $a->getId() . "\n";

引数オブジェクトの導入 (リファクタリング-p295)

2019-03-08

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「引数オブジェクトの導入」について書きます。

「引数オブジェクトの導入」 について

本来まとめて扱うべき一連の引数がある。

このような引数群をオブジェクトにすることは、データをグループ化してまとめる点で価値があります。またこのリファクタリングは、引数リストを短くできる点でも有益です。

変更前

<?php

date_default_timezone_set('Asia/Tokyo');

class Entry
{
    private $_value;
    private $_chargeDate;

    public function __construct($value, DateTime $chargeDate)
    {
        $this->_value = $value;
        $this->_chargeDate = $chargeDate;
    }

    public function getDate()
    {
        return $this->_chargeDate;
    }

    public function getValue()
    {
        return $this->_value;
    }
}

class Account
{
    private $_entiries = [];

    public function __construct()
    {
        $e1 = new Entry(10, new DateTime('2018/10/14'));
        $e2 = new Entry(20, new DateTime('2018/10/18'));
        $e3 = new Entry(30, new DateTime('2018/10/20'));
        $e4 = new Entry(40, new DateTime('2018/10/23'));
        $e5 = new Entry(50, new DateTime('2018/11/14'));

        $this->_entiries = [
            $e1,
            $e2,
            $e3,
            $e4,
            $e5,
        ];
    }

    public function getFlowBetween(DateTime $start, DateTime $end)
    {
        $result = 0;
        foreach ($this->_entiries as $entry) {
            if ($entry->getDate()->getTimeStamp() >= $start->getTimeStamp() &&
                $entry->getDate()->getTimeStamp() <= $end->getTimeStamp()) {
                $result += $entry->getValue();
            }
        }

        return $result;
    }
}

$anAccount = new Account();
$startDate = new DateTime('2018/10/14');
$endDate = new DateTime('2018/10/20');
echo $anAccount->getFlowBetween($startDate, $endDate) . "\n";

変更後

<?php

date_default_timezone_set('Asia/Tokyo');

class Entry2
{
    private $_value;
    private $_chargeDate;

    public function __construct($value, DateTime $chargeDate)
    {
        $this->_value = $value;
        $this->_chargeDate = $chargeDate;
    }

    public function getDate()
    {
        return $this->_chargeDate;
    }

    public function getValue()
    {
        return $this->_value;
    }
}

class Account2
{
    private $_entiries = [];

    public function __construct()
    {
        $e1 = new Entry2(10, new DateTime('2018/10/14'));
        $e2 = new Entry2(20, new DateTime('2018/10/18'));
        $e3 = new Entry2(30, new DateTime('2018/10/20'));
        $e4 = new Entry2(40, new DateTime('2018/10/23'));
        $e5 = new Entry2(50, new DateTime('2018/11/14'));

        $this->_entiries = [
            $e1,
            $e2,
            $e3,
            $e4,
            $e5,
        ];
    }

    public function getFlowBetween(DateRange $range)
    {
        $result = 0;
        foreach ($this->_entiries as $entry) {
            if ($range->includes($entry->getDate())) {
                $result += $entry->getValue();
            }
        }

        return $result;
    }
}

class DateRange
{
    private $_start;
    private $_end;

    public function __construct(DateTime $start, DateTime $end)
    {
        $this->_start = $start;
        $this->_end = $end;
    }

    public function getStart()
    {
        return $this->_start;
    }

    public function getEnd()
    {
        return $this->_end;
    }

    public function includes(DateTime $arg)
    {
        return ($arg->getTimeStamp() >= $this->_start->getTimeStamp()) &&
            ($arg->getTimeStamp() <= $this->_end->getTimeStamp())
        ;
    }
}

$anAccount = new Account2();
$startDate = new DateTime('2018/10/14');
$endDate = new DateTime('2018/10/20');

$range = new DateRange($startDate, $endDate);
echo $anAccount->getFlowBetween($range) . "\n";