ためすう

フィールドによるサブクラスの置き換え (リファクタリング-p232)

2018-12-23

目的

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

今回は「フィールドによるサブクラスの置き換え」について書きます。

「フィールドによるサブクラスの置き換え」 について

定数データを返すメソッドだけが異なるサブクラスがある。

変更前

<?php

abstract class Person
{
    abstract function isMale();
    abstract function getCode();
}

class Male extends Person
{
    public function isMale()
    {
        return true;
    }

    public function getCode()
    {
        return 'M';
    }
}

class Female extends Person
{
    public function isMale()
    {
        return false;
    }

    public function getCode()
    {
        return 'F';
    }
}

変更後

<?php

class Person2
{
    private $_isMale;
    private $_code;

    protected function __construct($isMale, $code)
    {
        $this->_isMale = $isMale;
        $this->_code = $code;
    }

    public static function createMale()
    {
        return new self(true, 'M');
    }

    public static function createFemale()
    {
        return new self(false, 'F');
    }

    public function isMale()
    {
        return $this->_isMale;
    }

    public function getCode()
    {
        return $this->_code;
    }
}

$kent = Person2::createMale();
echo (int)$kent->isMale() . "\n";
echo $kent->getCode() . "\n";

$ann = Person2::createFemale();
echo (int)$ann->isMale() . "\n";
echo $ann->getCode() . "\n";

State/Strategyによるタイプコードの置き換え (リファクタリング-p227)

2018-12-23

目的

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

今回は「State/Strategyによるタイプコードの置き換え」について書きます。

「State/Strategyによるタイプコードの置き換え」 について

クラスの振る舞いに影響するタイプコードがあるが、サブクラス化はできない。

タイプコードがそのオブジェクトの生存期間中に変化するか、サブクラス化が不都合な理由が別にあるときに適用できます。

変更前

<?php

class Employee
{
    private $_type;

    private $_monthlySalary;
    private $_commission;
    private $_bonus;

    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

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

    public function payAmount()
    {
        $this->_monthlySalary = 2000;
        $this->_commision = 1000;
        $this->_bonus = 3000;

        switch ($this->_type) {
            case self::ENGINEER:
                return $this->_monthlySalary;
            case self::SALESMAN:
                return $this->_monthlySalary + $this->_commission;
            case self::MANAGER:
                return $this->_monthlySalary + $this->_bonus;
            default:
                throw new Exception('不正な従業員');
        }
    }
}

$e1 = new Employee(2);
echo $e1->payAmount() . "\n";

変更後

<?php

abstract class EmployeeType
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    abstract function getTypeCode();

    public static function newType($code)
    {
        switch ($code) {
            case self::ENGINEER:
                return new Engineer();
            case self::SALESMAN:
                return new Salesman();
            case self::MANAGER:
                return new Manager();
            default:
                throw new Exception('不正な従業員コード');
        }
    }
}

class Engineer extends EmployeeType
{
    public function getTypeCode()
    {
        return self::ENGINEER;
    }
}

class Salesman extends EmployeeType
{
    public function getTypeCode()
    {
        return self::SALESMAN;
    }
}

class Manager extends EmployeeType
{
    public function getTypeCode()
    {
        return self::MANAGER;
    }
}

class Employee2
{
    private $_type;

    private $_monthlySalary;
    private $_commission;
    private $_bonus;

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

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

    public function setType($type)
    {
        $this->_type = EmployeeType::newType($type);
    }

    public function payAmount()
    {
        $this->_monthlySalary = 2000;
        $this->_commision = 1000;
        $this->_bonus = 3000;

        switch ($this->getType()) {
            case EmployeeType::ENGINEER:
                return $this->_monthlySalary;
            case EmployeeType::SALESMAN:
                return $this->_monthlySalary + $this->_commission;
            case EmployeeType::MANAGER:
                return $this->_monthlySalary + $this->_bonus;
            default:
                throw new Exception('不正な従業員');
        }
    }
}

$e2 = new Employee2(2);
echo $e2->payAmount() . "\n";

サブクラスによるタイプコードの置き換え (リファクタリング-p223)

2018-12-16

目的

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

今回は「サブクラスによるタイプコードの置き換え」について書きます。

「サブクラスによるタイプコードの置き換え」 について

クラスの振る舞いに影響を与える不変のタイプコードがある。

変更前

<?php

class Employee1
{
    private $_type;

    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

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

変更後

<?php

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

    abstract public function getType();

    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();
            default:
                throw new Exception('タイプコードの値が不正');
        }
    }
}

class Engineer extends Employee2
{
    public function getType()
    {
        return Employee2::ENGINEER;
    }
}

class Salesman extends Employee2
{
    public function getType()
    {
        return Employee2::SALESMAN;
    }
}

class Manager extends Employee2
{
    public function getType()
    {
        return Employee2::MANAGER;
    }
}

$e2 = Employee2::create(1);
echo $e2->getType() . "\n";

クラスによるタイプコードの置き換え (リファクタリング-p218)

2018-12-16

目的

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

今回は「クラスによるタイプコードの置き換え」について書きます。

「クラスによるタイプコードの置き換え」 について

振る舞いに影響しない数字のタイプコードを持つクラスがある。

この数字をクラスで置き換えれば、コンパイラはそのクラスについて型チェックができます。

変更前

<?php

class Person
{
    private $_bloodGroup;

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

    public function getBloodGroup()
    {
        return $this->_bloodGroup;
    }
}

// 呼び出し
$person = new Person(0);
echo $person->getBloodGroup() . "\n";

$person = new Person(2);
echo $person->getBloodGroup() . "\n";

変更後

<?php

class BloodGroup
{
    const O = 0;
    const A = 1;
    const B = 2;
    const AB = 3;

    private static $_values = [
        self::O,
        self::A,
        self::B,
        self::AB,
    ];

    private $_code;

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

    public function getCode()
    {
        return $this->_code;
    }

    private static function code($arg)
    {
        return $this->_values[$arg];
    }
}

class Person2
{
    private $_bloodGroup;

    public function __construct(BloodGroup $bloodGroup)
    {
        $this->_bloodGroup = $bloodGroup;
    }

    public function setBloodGroup(BloodGroup $arg)
    {
        $this->_bloodGroup = $arg;
    }

    public function getBloodGroupCode()
    {
        return $this->_bloodGroup->getCode();
    }
}

// 呼び出し
// PHP ではメンバ変数にクラスを設定できない
// BloodGroup クラスの呼び出し方を検討する
//
// Person クラスに BloodGroup クラスを注入する
$blood = new BloodGroup(BloodGroup::A);
$person = new Person2($blood);
echo $person->getBloodGroupCode() . "\n";

$blood = new BloodGroup(BloodGroup::B);
$person = new Person2($blood);
echo $person->getBloodGroupCode() . "\n";
<?php

データクラスによるレコードの置き換え (リファクタリング-p217)

2018-12-15

目的

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

今回は「データクラスによるレコードの置き換え」について書きます。

「データクラスによるレコードの置き換え」 について

この外部要素を扱うインターフェースを持つクラスを作ることが有効です。

見出し1 見出し2 見出し3
item1 center right
item2 abc def

<?php

class Dummy
{
    private $items = [];

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

    public function getItems()
    {
        return $this->items;
    }
}

// 古いプログラミング環境のレコード構造
$data = [
    [
        'item1', 'center', 'right'
    ],
    [
        'item2', 'abc', 'def',
    ],
];

// Dummy クラスを介してやり取りする
$dummy = new Dummy($data);
print_r($dummy->getItems());

Swift の第一引数がある関数を Objective-C で呼び出すときの注意

2018-12-09

目的

Swift で第一引数がある関数を定義して、 Objective-C で呼び出すとき何度か嵌ったのでメモしておきます。

嵌った例

Swift の関数

@objcMembers class Calc: NSObject {
    static func add(a:Int, b:Int) -> Int {
        return a + b
    }
}

Objective-C 側

[Calc add:2 b:3];

下記のようなエラーが発生しました。

No visible @interface for 'Calc' declares the selector 'add:b:'

解決した方法

Swift の関数の第一引数にラベルをつけない指定をしました。

Objective-C の場合は第一引数にラベルをつけないため、_ を指定する必要があります。

@objcMembers class Calc: NSObject
    static func add(_ a:Int, b:Int) -> Int {
        return a + b
    }
}

Objective-C 側は変えずに下記で呼び出せます

[Calc add:2 b:3];

@objcMembers について

class の前の @objcMembers は、 Swift のコードを Objective-C のコードを呼び出すために必要です。

参考

Objective-C の import について

2018-12-09

目的

iOS アプリを開発しているとき、import の記述方法が2つあるので調べました。

例えば、 Hoge というモジュールを読み込みたいとします。

@import について

@import Hoge;

Hoge 配下のモジュールが利用可能になります。

プロジェクト > Build Settings > Enable Modules が Yes になっている必要があります。

#import について

#import "<Hoge/Moge.h>"

Hoge 配下の Moge を読み込みます。

紛らわしいですが、下記でも呼べます。

@import Hoge.Moge;

参考

フィールドのカプセル化 (リファクタリング-p206)

2018-12-09

目的

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

今回は「フィールドのカプセル化」について書きます。

「フィールドのカプセル化」 について

データを公開すると、それを所有しているオブジェクトが知らないうちに、他のオブジェクトから値を変更されたりアクセスされたりします。

データが振る舞いから分離されてしまいます。

アクセサしか持たないクラスは、オブジェクト指向の利点を真に活かしていない無意味なクラスであり、オブジェクト指向技術は単に浪費するだけの恐ろしいものになってしまいます。

変更前

<?php

class SampleBefore
{
    public $_name;
}

変更後

<?php

class SampleAfter
{
    private $_name;

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

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

$c1 = new SampleBefore();
$c1->_name = 'name1';
echo $c1->_name . "\n";

$c2 = new SampleAfter();
$c2->setName('name2');
echo $c2->getName() . "\n";

Redshift のデータを CSV 出力する方法

2018-12-09

目的

Redshift のデータを CSV ファイル出力します。

方法

SQL を実行する

$ psql -U "[USER]" -h [xxxx.redshift.amazonaws.com] -p 5439 -d [DBNAME] -c "SELECT NOW();"

外部ファイルの SQL を実行する

SQL ファイルを用意する

$ vim ~/test.sql

SELECT NOW();
$ psql -U "[USER]" -h [xxxx.redshift.amazonaws.com] -p 5439 -d [DBNAME] -c ~/test.sql

ファイルに出力する

$ psql -U "[USER]" -h [xxxx.redshift.amazonaws.com] -p 5439 -d [DBNAME] -c ~/test.sql > ./test-result.csv

参考

単方向関連の双方向への変更 (リファクタリング-p197)

2018-12-08

目的

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

今回は「単方向関連の双方向への変更」について書きます。

「単方向関連の双方向への変更」 について

2つのクラスが互いにその特性を使う必要があるが、単方向のリンクしかない。

このイディオムは、これでいいと思えるまで何度もテストしなければならないので、扱いにくいと思います。

変更前

<?php

class Order
{
    /* @var Customer */
    public $_customer;

    public function getCustomer()
    {
        return $this->_customer;
    }

    public function setCustomer(Customer $customer)
    {
        $this->_customer = $customer;
    }
}

class Customer
{
}

$c = new Customer();
$o = new Order();
$o->setCustomer($c);

echo get_class($o->getCustomer()) . "\n";

変更後

<?php

class Order2
{
    /* @var Customer2 */
    public $_customer;

    public function getCustomer()
    {
        return $this->_customer;
    }

    public function setCustomer(Customer2 $customer)
    {
        if (!is_null($this->_customer)) {
            // このコードは動かない 擬似コード
            // $this->_customer->friendOrders()->remove($this);
        }
        $this->_customer = $customer;
        if (!is_null($this->_customer)) {
            // このコードは動かない 擬似コード
            // $this->_customer->friendOrders()->add($this);
        }
    }
}

class Customer2
{
    private $_orders = [];

    public function friendOrders()
    {
        // 関連を変更するときは、Orderオブジェクトだけがこれを使うこと
        return $this->_orders;
    }
}
$c = new Customer2();
$o = new Order2();
$o->setCustomer($c);

echo get_class($o->getCustomer()) . "\n";