ためすう

値から参照への変更 (リファクタリング-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";

PHP で Web API のレスポンスを扱う方法

2018-11-25

目的

外部の API を利用して、レスポンスを受け取る時に良い方法はないかと

現時点での考えをまとめました。

やってみる

方法1. 配列で扱う

<?php

// API を実行して、下記のレスポンスを取得するという想定
$json = '[
    {
        "date": "2018-11-12 00:00:00",
        "result": "item1"
    },
    {
        "date": "2018-11-12 11:23:00",
        "result": "item2"
    }
]';

var_dump(json_decode($json, true)) . "\n";

出力

array(2) {
  [0]=>
  array(2) {
    ["date"]=>
    string(19) "2018-11-12 00:00:00"
    ["result"]=>
    string(5) "item1"
  }
  [1]=>
  array(2) {
    ["date"]=>
    string(19) "2018-11-12 11:23:00"
    ["result"]=>
    string(5) "item2"
  }
}

方法2. stdClass で扱う

<?php

// API を実行して、下記のレスポンスを取得するという想定
$json = '[
    {
        "date": "2018-11-12 00:00:00",
        "result": "item1"
    },
    {
        "date": "2018-11-12 11:23:00",
        "result": "item2"
    }
]';

var_dump(json_decode($json)) . "\n";

出力

array(2) {
  [0]=>
  object(stdClass)#1 (2) {
    ["date"]=>
    string(19) "2018-11-12 00:00:00"
    ["result"]=>
    string(5) "item1"
  }
  [1]=>
  object(stdClass)#2 (2) {
    ["date"]=>
    string(19) "2018-11-12 11:23:00"
    ["result"]=>
    string(5) "item2"
  }
}

方法3. 独自のクラスにセットする

方法1、方法2ともに API の仕様が変わると意図しない動作をする可能性があります。

外部 API を利用している場合は、自作のクラスで API のレスポンスを入れるのが良いかもしれません。

<?php

// API を実行して、下記のレスポンスを取得するという想定
$json = '[
    {
        "date": "2018-11-12 00:00:00",
        "result": "item1"
    },
    {
        "date": "2018-11-12 11:23:00",
        "result": "item2"
    }
]';

class Item
{
    private $date;
    private $result;

    public function __construct($data)
    {
        $this->date = !isset($data['date']) ?: $data['date'];
        $this->result = !isset($data['result']) ?: $data['result'];
    }
}

class ItemCollection extends ArrayObject
{
    public function offsetSet($offset, $value)
    {
        if (!is_object($value) || get_class($value) !== 'Item') {
            throw new InvalidArgumentException;
        }
        return parent::offsetSet($offset, $value);
    }
}

$rows = json_decode($json, true);

$data = new ItemCollection();

foreach ($rows as $row) {
    $item = new Item($row);
    $data[] = $item;
}

var_dump($data);

出力

object(ItemCollection)#1 (1) {
  ["storage":"ArrayObject":private]=>
  array(2) {
    [0]=>
    object(Item)#2 (2) {
      ["date":"Item":private]=>
      string(19) "2018-11-12 00:00:00"
      ["result":"Item":private]=>
      string(5) "item1"
    }
    [1]=>
    object(Item)#3 (2) {
      ["date":"Item":private]=>
      string(19) "2018-11-12 11:23:00"
      ["result":"Item":private]=>
      string(5) "item2"
    }
  }
}

データ構造を固定できました。

参考

iOS アプリ開発まわりの語彙整理

2018-11-24

目的

Web アプリの開発を主にやってきましたが、iOS アプリの開発も少しやることになったので、iOS アプリ開発まわりの語彙を整理します。

パッケージマネージャー

CocoaPods

Swift と Objective-C の Cocoa プロジェクトのパッケージマネージャーです。

Carthage

分散型のパッケージマネージャーです。

Swift Package Manager

公式のパッケージマネージャーです。

fastlane

CI 環境の構築で利用します。

Fabric というサービスの中に取り込まれています。

Fabric

クラッシュレポート (Crashlytics) などで構成されているサービスです。

Bitrise

モバイルアプリに特化した CI サービスです。

Firebase

Firebaseとは、すばやく高品質のモバイルアプリを開発することができるプラットフォームで、開発に役立つ数多くの機能が用意されています。

Google が提供しているサービスです。

Google は Fabric の買収も行ったようです。

また、Google Analytics との連携も行うことが出来ます。

参考

Repro と F.O.X

2018-11-24

目的

モバイルアプリの開発をする上で、耳にする Repro と F.O.X について書きます。

Repro について

「アナリティクス」機能

ダウンロード数やアクセス数を計測する定量分析、ユーザーがアプリを使っている様子を観察する定性分析

「マーケティング」機能

ターゲットユーザーに対して、プッシュ通知やアプリ内メッセージ送信など

F.O.X について

スマートフォンアプリ向け広告効果計測

最後に

下記によれば、F.O.X で計測した広告効果をもとに

Repro のプッシュ通知、アプリ内メッセージなどが利用できるようです。

スマートフォン効果計測ツール「F.O.X」がモバイルアプリ向け分析・マーケティングツール「Repro」と連携 〜ユーザー分析に基づいたアプリ内マーケティングを支援〜 | CyberZ|スマートフォン広告マーケティング事業

参考

MySQL で月初と月末の日付を取得する方法

2018-11-18

目的

MySQL からデータを月次で抽出する必要がありました。

ある日にちから、月初と月末を取得する方法を調べました。

方法

SET @TARGET_DATE='2018/11/18';

-- @TARGET_DATE 月の月初
SELECT DATE_FORMAT(@TARGET_DATE, '%Y-%m-01');

-- @TARGET_DATE 月の月末
SELECT LAST_DAY(@TARGET_DATE);

出力結果

mysql> SET @TARGET_DATE='2018/11/18';
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> -- @TARGET_DATE 月の月初
mysql> SELECT DATE_FORMAT(@TARGET_DATE, '%Y-%m-01');
+---------------------------------------+
| DATE_FORMAT(@TARGET_DATE, '%Y-%m-01') |
+---------------------------------------+
| 2018-11-01                            |
+---------------------------------------+
1 row in set (0.00 sec)

mysql>
mysql> -- @TARGET_DATE 月の月末
mysql> SELECT LAST_DAY(@TARGET_DATE);
+------------------------+
| LAST_DAY(@TARGET_DATE) |
+------------------------+
| 2018-11-30             |
+------------------------+
1 row in set (0.00 sec)

また、1ヶ月後、2ヶ月後を計算するときは、

SET @TARGET_DATE='2018/11/18';

SELECT DATE_FORMAT(ADDDATE(@TARGET_DATE, INTERVAL 1 MONTH), '%Y-%m-01');

出力結果

mysql> SELECT DATE_FORMAT(ADDDATE(@TARGET_DATE, INTERVAL 1 MONTH), '%Y-%m-01')
    -> ;
+------------------------------------------------------------------+
| DATE_FORMAT(ADDDATE(@TARGET_DATE, INTERVAL 1 MONTH), '%Y-%m-01') |
+------------------------------------------------------------------+
| 2018-12-01                                                       |
+------------------------------------------------------------------+
1 row in set (0.00 sec)

参考

委譲の隠蔽 (リファクタリング-p157)

2018-11-18

目的

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

今回は「委譲の隠蔽」について書きます。

「委譲の隠蔽」 について

クライアントがあるオブジェクトの委譲クラスをコールしている。

カプセル化は、唯一とは言わないまでも、オブジェクト指向技術の鍵です。

変更前

<?php

class Person
{
    private $_department;

    private $_name;

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

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

    public function getDepartment()
    {
        return $this->_department;
    }

    public function setDepartment(Department $arg)
    {
        $this->_department = $arg;
    }
}

class Department
{
    private $_chargeCode;

    /* @var Person */
    private $_manager;

    public function __construct(Person $manager)
    {
        $this->_manager = $manager;
    }

    public function getManager()
    {
        return $this->_manager;
    }
}

$john = new Person('john');

$manager_aaa = new Person('manager_aaa');
$department = new Department($manager_aaa);

$john->setDepartment($department);
$manager = $john->getDepartment()->getManager();

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

変更後

<?php

class Person2
{
    private $_department;

    private $_name;

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

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

    public function setDepartment(Department2 $arg)
    {
        $this->_department = $arg;
    }

    public function getManager()
    {
        return $this->_department->getManager();
    }
}

class Department2
{
    private $_chargeCode;

    /* @var Person */
    private $_manager;

    public function __construct(Person2 $manager)
    {
        $this->_manager = $manager;
    }

    public function getManager()
    {
        return $this->_manager;
    }
}

$john = new Person2('john');

$manager_aaa = new Person2('manager_aaa');
$department = new Department2($manager_aaa);

$john->setDepartment($department);
$manager = $john->getManager();

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