ためすう

コンストラクタ本体の引き上げ (リファクタリング-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";

メソッドによる引数の置き換え (リファクタリング-p292)

2019-03-07

目的

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

今回は「メソッドによる引数の置き換え」について書きます。

「メソッドによる引数の置き換え」 について

あるオブジェクトがメソッドを呼び出し、その戻り値を別のメソッドの引数として渡している。受信側は、そのメソッドを呼び出し可能である。

もしメソッドが、引数で渡される値を別の方法で取得できるならば、そのように修正すべきです。

変更前

<?php

class Sample
{
    private $_quantity;
    private $_itemPrice;

    public function __construct($quantity, $itemPrice)
    {
        $this->_quantity = $quantity;
        $this->_itemPrice = $itemPrice;
    }

    public function getPrice()
    {
        $basePrice = $this->_quantity * $this->_itemPrice;
        $discountLevel = 1;
        if ($this->_quantity > 100) {
            $discountLevel = 2;
        }

        return $this->discountedPrice($basePrice, $discountLevel);
    }

    private function discountedPrice($basePrice, $discountLevel)
    {
        if ($discountLevel === 2) {
            return $basePrice * 0.1;
        }

        return $basePrice * 0.05;
    }
}

$s = new Sample(10, 2000);
echo $s->getPrice() . "\n";

変更後

<?php

class Sample2
{
    private $_quantity;
    private $_itemPrice;

    public function __construct($quantity, $itemPrice)
    {
        $this->_quantity = $quantity;
        $this->_itemPrice = $itemPrice;
    }

    public function getPrice()
    {
        return $this->discountedPrice();
    }

    private function getBasePrice()
    {
        return $this->_quantity * $this->_itemPrice;
    }
    private function getDiscountLevel()
    {
        $discountLevel = 1;
        if ($this->_quantity > 100) {
            $discountLevel = 2;
        }

        return $discountLevel;
    }

    private function discountedPrice()
    {
        $basePrice = $this->getBasePrice();
        if ($this->getDiscountLevel() === 2) {
            return $basePrice * 0.1;
        }

        return $basePrice * 0.05;
    }
}

$s = new Sample2(10, 2000);
echo $s->getPrice() . "\n";

オブジェクトそのものの受け渡し (リファクタリング-p288)

2019-03-05

目的

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

今回は「オブジェクトそのものの受け渡し」について書きます。

「オブジェクトそのものの受け渡し 」 について

あるオブジェクトから複数の値を取得し、それらの値をメソッド呼び出しの引数として渡している。

「オブジェクトそのものの受け渡し」は、引数リストを変更に対して頑強にするだけでなく、しばしばコードを読みやすくします。

変更前

<?php

class TempRange
{
    private $_low;
    private $_high;

    public function __construct($low, $high)
    {
        $this->_low = $low;
        $this->_high = $high;
    }

    public function getLow()
    {
        return $this->_low;
    }

    public function getHigh()
    {
        return $this->_high;
    }
}

class Room
{
    public function withinPlan(HeatingPlan $plan)
    {
        $low = $this->daysTempRange()->getLow();
        $high = $this->daysTempRange()->getHigh();

        return $plan->withinRange($low, $high);
    }

    // 現在までの気温範囲
    private function daysTempRange()
    {
        return new TempRange(10, 50);
    }
}

class HeatingPlan
{
    /* @var TempRange */
    private $_range;

    public function __construct($range)
    {
        $this->_range = $range;
    }

    public function withinRange($low, $high)
    {
        return (
            $low >= $this->_range->getLow() &&
            $high <= $this->_range->getHigh()
        );
    }
}

// 暖房計画の気温範囲
$temp_range = new TempRange(10, 70);
$hp = new HeatingPlan($temp_range);

$r = new Room();
echo (int)$r->withinPlan($hp) . "\n";

変更後

<?php

class TempRange2
{
    private $_low;
    private $_high;

    public function __construct($low, $high)
    {
        $this->_low = $low;
        $this->_high = $high;
    }

    public function getLow()
    {
        return $this->_low;
    }

    public function getHigh()
    {
        return $this->_high;
    }

    public function includes(TempRange2 $arg)
    {
        return (
            $arg->getLow() >= $this->getLow() &&
            $arg->getHigh() <= $this->getHigh()
        );
    }
}
class Room2
{
    public function withinPlan(HeatingPlan2 $plan)
    {
        return $plan->withinRange($this->daysTempRange());
    }

    // 現在までの気温範囲
    private function daysTempRange()
    {
        return new TempRange2(10, 50);
    }
}

class HeatingPlan2
{
    /* @var TempRange2 */
    private $_range;

    public function __construct($range)
    {
        $this->_range = $range;
    }

    public function withinRange(TempRange2 $room_range)
    {
        return $this->_range->includes($room_range);
    }
}

// 暖房計画の気温範囲
$temp_range = new TempRange2(10, 70);
$hp2 = new HeatingPlan2($temp_range);

$r2 = new Room2();
echo (int)$r2->withinPlan($hp2) . "\n";

明示的なメソッド群による引数の置き換え (リファクタリング-p285)

2019-03-04

目的

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

今回は「明示的なメソッド群による引数の置き換え」について書きます。

「明示的なメソッド群による引数の置き換え」 について

引数の特定の値によって異なるコードが実行されるメソッドがある。

引数の値に対応する別々のメソッドを作成する

変更前

<?php

class Engineer extends Employee
{
    public function __consturct() {}
}

class Employee
{
    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('不正なタイプコード');
    }
}

$e = Employee::create(0);
echo get_class($e) . "\n";

変更後

<?php

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

    public static function createEngineer()
    {
        return new Engineer();
    }
}

$e = Employee2::createEngineer();
echo get_class($e) . "\n";

メソッドのパラメータ化 (リファクタリング-p283)

2019-03-03

目的

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

今回は「メソッドのパラメータ化」について書きます。

「メソッドのパラメータ化」 について

複数のメソッドが、異なる値に対してよく似た振る舞いをしている。

変更前

<?php

class Employee
{
    private $salary;

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

    public function tenPercentRaise()
    {
        $this->salary *= 1.1;
    }

    public function fivePercentRaise()
    {
        $this->salary *= 1.05;
    }

    public function getSalary()
    {
        return $this->salary;
    }
}

$e1 = new Employee(1000);
$e1->tenPercentRaise();
echo $e1->getSalary() . "\n";

変更後

<?php

class Employee2
{
    private $salary;

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

    public function raise($factor)
    {
        $this->salary *= (1 + $factor);
    }

    public function getSalary()
    {
        return $this->salary;
    }
}

$e2 = new Employee2(1000);
$e2->raise(0.1);
echo $e2->getSalary() . "\n";