ためすう

フィールドのカプセル化 (リファクタリング-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";

オブジェクトによる配列の置き換え (リファクタリング-p186)

2018-12-08

目的

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

今回は「オブジェクトによる配列の置き換え」について書きます。

「オブジェクトによる配列の置き換え」 について

配列の各要素が、それぞれ異なる意味を持っている。

配列はデータを組織するための一般的な構造ですが、ある順で並んだ同種のオブジェクトのコレクションを保持するときだけに使うべきです。

変更前

<?php

$row = [];
$row[0] = 'Liverpool';
$row[1] = 15;

$name = $row[0];
$wins = $row[1];

echo 'name: ' . $name . "\n";
echo 'wins: ' . $wins . "\n";

変更後

<?php

class Performance
{
    private $_data = [];

    private $_name;
    private $_wins;

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

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

    public function getWins()
    {
        return $this->_wins;
    }

    public function setWins($arg)
    {
        $this->_wins = $arg;
    }
}

$row = new Performance();
$row->setName('Liverpool');
$row->setWins(15);

$name = $row->getName();
$wins = $row->getWins();

echo 'name: ' . $name . "\n";
echo 'wins: ' . $wins . "\n";

参照から値への変更 (リファクタリング-p183)

2018-12-02

目的

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

今回は「参照から値への変更」について書きます。

「参照から値への変更」 について

小さくて、不変で、コントロールが煩わしい参照オブジェクトがある。

値オブジェクトは、特に分散並列処理システムで有効です。

変更前

<?php

class Currency
{
    private $_code;

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

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

$r = (new Currency('USD')) === (new Currency('USD'));
var_dump($r);

変更後

<?php

class Currency2
{
    private $_code;

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

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

    public function equals($arg)
    {
        if (!($arg instanceOf Currency2)) {
            return false;
        }

        $other = $arg;
        return $this->_code === $other->_code;
    }
}

$r = (new Currency2('USD'))->equals(new Currency2('USD'));
var_dump($r);

値から参照への変更 (リファクタリング-p179)

2018-12-02

目的

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

今回は「値から参照への変更」について書きます。

「値から参照への変更」 について

同じインスタンスが多数存在するクラスがある。それらを1つのオブジェクトに置き換えたい。

多くのシステムにおいて、たとえば参照オブジェクトと値オブジェクトというような分類は有効です。

変更前

<?php

class Customer
{
    private $_name;

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

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

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

    public function __construct($customerName)
    {
        $this->_customer = new Customer($customerName);
    }

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

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

class Client
{
    public static function numberOfOrdersFor($orders, $customer)
    {
        $result = 0;
        foreach ($orders as $order) {
            if ($order->getCustomerName() === $customer) {
                $result++;
            }
        }

        return $result;
    }
}

$orders = [
    new Order('customer1'),
    new Order('customer2'),
    new Order('customer3'),
    new Order('customer2'),
];

echo Client::numberOfOrdersFor($orders, 'customer2') . "\n";

変更後

<?php

class Customer2
{
    private $_name;

    // 説明を簡単にするため、ここに保持する
    private static $_instances;

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

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

    public static function getNamed($name)
    {
        // 動かすようにした、本来はどこに置くべきか
        self::loadCustomers();

        return self::$_instances[$name];
    }

    private function store()
    {
        self::$_instances[$this->getName()] = $this;
    }

    public static function loadCustomers()
    {
        (new Customer2('customer1'))->store();
        (new Customer2('customer2'))->store();
        (new Customer2('customer3'))->store();
    }
}

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

    public function __construct($customerName)
    {
        $this->_customer = Customer2::getNamed($customerName);
    }

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

    public function setCustomer($customerName)
    {
        $this->_customer = new Customer2($customerName);
    }
}

class Client2
{
    public static function numberOfOrdersFor($orders, $customer)
    {
        $result = 0;
        foreach ($orders as $order) {
            if ($order->getCustomerName() === $customer) {
                $result++;
            }
        }

        return $result;
    }
}

$orders = [
    new Order2('customer1'),
    new Order2('customer2'),
    new Order2('customer3'),
    new Order2('customer2'),
];

echo Client2::numberOfOrdersFor($orders, 'customer2') . "\n";

オブジェクトによるデータ値の置き換え (リファクタリング-p175)

2018-12-02

目的

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

今回は「オブジェクトによるデータ値の置き換え」について書きます。

「オブジェクトによるデータ値の置き換え」 について

追加のデータや振る舞いが必要なデータ項目がある。

変更前

<?php

class Order
{
    private $_customer;

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

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

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

class Client
{
    public static function numberOfOrdersFor($orders, $customer)
    {
        $result = 0;
        foreach ($orders as $order) {
            if ($order->getCustomer() === $customer) {
                $result++;
            }
        }

        return $result;
    }
}

$orders = [
    new Order('customer1'),
    new Order('customer2'),
    new Order('customer3'),
    new Order('customer2'),
];

echo Client::numberOfOrdersFor($orders, 'customer2') . "\n";

変更後

<?php

class Customer2
{
    private $_name;

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

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

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

    public function __construct($customerName)
    {
        $this->_customer = new Customer2($customerName);
    }

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

    public function setCustomer($customerName)
    {
        $this->_customer = new Customer2($customerName);
    }
}

class Client2
{
    public static function numberOfOrdersFor($orders, $customer)
    {
        $result = 0;
        foreach ($orders as $order) {
            if ($order->getCustomerName() === $customer) {
                $result++;
            }
        }

        return $result;
    }
}

$orders = [
    new Order2('customer1'),
    new Order2('customer2'),
    new Order2('customer3'),
    new Order2('customer2'),
];

echo Client2::numberOfOrdersFor($orders, 'customer2') . "\n";

自己カプセル化フィールド (リファクタリング-p171)

2018-12-02

目的

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

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

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

フィールドのアクセス方法には2つの流派があります。 1つは、その変数を定義しているクラス内の範囲内なら自由にアクセスしてよいとする派です。 もう1つは、クラス内であっても必ずアクセサを使うべきであるとする派です。

「自己カプセル化フィールド」を行う最も重要なタイミングは、スーパークラスのフィールドをアクセスしていて、その変数アクセスをサブクラス内の計算値で置き換えたいと思ったときです。

変更前

<?php

class IntRange
{
    private $_low;
    private $_high;

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

    public function includes($arg)
    {
        return $arg >= $this->_low && $arg <= $this->_high;
    }

    public function grow($factor)
    {
        $this->_high = $this->_high * $factor;
    }
}

$ir = new IntRange(20, 30);
echo (int)$ir->includes(21) . "\n";

変更後

<?php

class IntRange2
{
    private $_low;
    private $_high;

    public function __construct($low, $high)
    {
        $this->initialize($low, $high);
    }

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

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

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

    public function setLow($arg)
    {
        $this->_low = $arg;
    }

    public function setHigh($arg)
    {
        $this->_high = $arg;
    }

    public function includes($arg)
    {
        return $arg >= $this->getLow() && $arg <= $this->getHigh();
    }

    public function grow($factor)
    {
        $this->setHigh($this->getHigh() * $factor);
    }
}

$ir = new IntRange2(20, 30);
echo (int)$ir->includes(21) . "\n";

局所的拡張の導入 (リファクタリング-p164)

2018-12-01

目的

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

今回は「局所的拡張の導入」について書きます。

「局所的拡張の導入」 について

それらの追加されるメソッドを備えた新たなクラスを作る。この拡張クラスは、元のクラスのサブクラスまたはラッパーである。

変更前

<?php
date_default_timezone_set('Asia/Tokyo');

class Client
{
    public static function test()
    {
        $dt = new DateTime('2018/10/14');
        $nextDay = self::nextDay($dt);
        echo $nextDay->format('Y/m/d') . "\n";
    }

    private static function nextDay(DateTime $datetime)
    {
        return $datetime->modify('+1 days');
    }
}

Client::test();

変更後

<?php
date_default_timezone_set('Asia/Tokyo');

// サブクラスを使う場合
class MfDateSub2 extends DateTime
{
    public function nextDay()
    {
        return $this->modify('+1 days');
    }
}

class Client2
{
    public static function test()
    {
        $dt = new MfDateSub2('2018/10/14');
        echo $dt->nextDay()->format('Y/m/d') . "\n";
    }
}

Client2::test();
echo '---' . "\n";

// 委譲を使う場合
class Client3
{
    public static function test()
    {
        $dt = new DateTime('2018/10/14');
        $mdw = new MfDateWrap3($dt);
        echo $mdw->nextDay()->format('Y/m/d') . "\n";
    }
}

class MfDateWrap3
{
    /* @var DateTime */
    private $_original;

    public function __construct(DateTime $arg)
    {
        $this->_original = $arg;
    }

    public function nextDay()
    {
        return $this->_original->modify('+1 days');
    }
}

Client3::test();

外部メソッドの導入 (リファクタリング-p162)

2018-11-25

目的

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

今回は「外部メソッドの導入」について書きます。

変更前

<?php

date_default_timezone_set('Asia/Tokyo');
// サーバー側のコードという想定
class PreviousEnd
{
    private $_year;
    private $_month;
    private $_date;

    public function __construct($datetime)
    {
        $this->_year = $datetime->format('Y');
        $this->_month = $datetime->format('m');
        $this->_date = $datetime->format('d');
    }

    public function getYear()
    {
        return $this->_year;
    }

    public function getMonth()
    {
        return $this->_month;
    }

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

// クライアント側
$targetDate = '2018/10/11';
$previousEnd = new PreviousEnd(new DateTime($targetDate));
$newStart = (new DateTime($targetDate))->setDate(
    $previousEnd->getYear(),
    $previousEnd->getMonth(),
    $previousEnd->getDate() + 1
);

echo $newStart->format('Y/m/d') . "\n";
// 本来はサーバー側のコードを修正したい

変更後

<?php

date_default_timezone_set('Asia/Tokyo');

// サーバー側のコードという想定
class PreviousEnd
{
    private $_year;
    private $_month;
    private $_date;

    public function __construct($datetime)
    {
        $this->_year = $datetime->format('Y');
        $this->_month = $datetime->format('m');
        $this->_date = $datetime->format('d');
    }

    public function getYear()
    {
        return $this->_year;
    }

    public function getMonth()
    {
        return $this->_month;
    }

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

$targetDate = '2018/10/11';
$previousEnd = new PreviousEnd(new DateTime($targetDate));
$newStart = nextDay($targetDate, $previousEnd);

function nextDay($targetDate, $arg)
{
    return (new DateTime($targetDate))->setDate(
        $arg->getYear(),
        $arg->getMonth(),
        $arg->getDate() + 1
    );
}

echo $newStart->format('Y/m/d') . "\n";