ためすう

メソッドのインライン化 (リファクタリング-p117)

2018-10-28

目的

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

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

「メソッドのインライン化」 について

「メソッドのインライン化(117)」を適用するのは、間接化し過ぎた結果、 すべてのメソッドが別のメソッドへと単純に委譲しているようにしか見えず、 委譲の途中で道に迷ってしまうようなときです。

変更前

class Sample
{
    public $_numberOfLateDeliveries = 3;

    public function getRating()
    {
        return ($this->moreThanFiveLateDeliveries()) ? 2 : 1;
    }

    private function moreThanFiveLateDeliveries()
    {
        return $this->_numberOfLateDeliveries > 5;
    }
}

// 呼び出し
$sample = new Sample();
echo $sample->getRating() . "\n";

変更後

class SampleNew
{
    public $_numberOfLateDeliveries = 3;

    public function getRating()
    {
        return ($this->_numberOfLateDeliveries > 5) ? 2 : 1;
    }
}

// 呼び出し
$sample_new = new SampleNew();
echo $sample->getRating() . "\n";

メソッドの抽出 (リファクタリング-p110)

2018-10-28

目的

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

今回は「メソッドの抽出」について書きます。

「メソッドの抽出」 について

どんな処理をするかではなく、何をするかによって命名する

私はメソッドが返す値は1つにする方がよいと思うので、複数のメソッドを準備して、さまざまな値に対応することを試みます。

変更前

class Sample
{
    private $_name;

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

    public function printOwing($amount)
    {
        $this->printBanner();

        // 明細の表示
        echo('name: ' . $this->_name . "\n");
        echo('amount: ' . $amount . "\n");
    }

    public function printBanner()
    {
        echo 'call' . __FUNCTION__;
    }
}

// 呼び出し
$obj = new Sample('MyName');
$obj->printOwing(2000);

変更後

class SampleNew
{
    private $_name;

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

    public function printOwing($amount)
    {
        $this->printBanner();
        $this->printDetails($amount);
    }

    // 明細の表示
    public function printDetails($amount)
    {
        echo('name: ' . $this->_name . "\n");
        echo('amount: ' . $amount . "\n");
    }

    public function printBanner()
    {
        echo 'call' . __FUNCTION__;
    }
}

// 呼び出し
$obj_new = new SampleNew('MyName');
$obj_new->printOwing(4000);

unix で script コマンドを使ってみる

2018-10-27

目的

unix でコマンドを実行した後、出力結果まで保存することができないか調べたところ

script コマンドというものがあったので使ってみます。

使ってみる

$ script /tmp/test.log
スクリプトを開始しました、ファイルは /tmp/test.log です
$ hostname
localhost.localdomain
$ pwd
/home/vagrant
$ date
2018年  3月  4日 日曜日 22:26:29 UTC
$ exit
exit
スクリプトを終了しました、ファイルは /tmp/test.log です

書き出しファイルを読み込みます

$ less -r /tmp/test.log

この -r オプションは、下記の目的で使用します。

「そのままの」制御文字を表示させるようにする。

参考URL

シェルで変数置換する方法

2018-10-22

目的

シェルスクリプトの中で変数の値を変換します。

やってみる

方法1. sedで変換する

#/bin/bash

TEST="abc abcdedfg 345 987 22222 345622"
echo "sedで置換する"
echo "変更前: "  $TEST
echo "変更後: "  $TEST | sed s/abc/ABC/g
echo ""

結果

変更前:  abc abcdedfg 345 987 22222 345622
変更後:  ABC ABCdedfg 345 987 22222 345622

方法2. シェル変数展開時に変換する

TEST="abc 'abcdedfg' 345 987 22222 345622"
echo "シェル変数展開時に置換"
echo "変更前: "  $TEST
echo "変更後 1箇所: "  ${TEST/\'/\\\'}
echo "変更後 全箇所: " ${TEST//\'/\\\'}

結果

変更前:  abc 'abcdedfg' 345 987 22222 345622
変更後 1箇所:  abc \'abcdedfg' 345 987 22222 345622
変更後 全箇所:  abc \'abcdedfg\' 345 987 22222 345622

参考URL

PHP で正規表現を使う

2018-10-21

目的

php で下記の条件で判定を行いたい時がありました。

  • ファイル名で拡張子が jpg, jpeg, gif, png 
  • .で始まらない

explode を使って配列にしてファイル名を文字列比較してたのですが、正規表現だともっと簡単に出来そうなので正規表現でやってみました。

正規表現でやってみる

<?php

$files = [
    'test.jpg',
    'test.jpeg',
    '222test.gif',
    '333test.png',
    '.test.jpg'
];

foreach ($files as $v) {
    echo $v . "\n";
    if (preg_match("/^[^\.]+\.(jpe?g|gif|png)$/", $v)) {
        echo "A match was found.";
    } else {
        echo "A match was not found.";
    }
echo "\n\n";
}

実行結果

test.jpg
A match was found.

test.jpeg
A match was found.

222test.gif
A match was found.

333test.png
A match was found.

.test.jpg
A match was not found.

今回使用した正規表現パターンについて

| | | |-|— | |^ |行頭 | |$ |行末 | |[^abc] |a、b、c以外の1文字 | |+ |1回以上の繰り返し | |? |0回または1回の出現 |

参考URL

PHP でデザインパターン (Abstract Factory)

2018-10-21

Abstract Factory パターンとは

具体的なクラスを明確にすることなく、関連し合うオブジェクトの集まりを生成するパターン

オブジェクト指向プログラミングでは、具体的なクラスではなくインターフェースや抽象化されたクラスのAPIに対してプログラミングすることが重要になります

Abstract Factory パターンのメリット

  • 具体的なクラスをクライアントから隠蔽する
  • 利用する部品群の整合性を保つ
  • 部品群の単位で切り替えができる

実装例

<?php
interface DaoFactory
{
    public function createItemDao();
    public function createOrderDao();
}

class DbFactory implements DaoFactory
{
    public function createItemDao()
    {
        return new DbItemDao();
    }

    public function createOrderDao()
    {
        return new DbOrderDao($this->createItemDao());
    }
}

class MockFactory implements DaoFactory
{
    public function createItemDao()
    {
        return new MockItemDao();
    }

    public function createOrderDao()
    {
        return new MockOrderDao();
    }
}

interface ItemDao
{
    public function findById($item_id);
}

interface OrderDao
{
    public function findById($order_id);
}

class DbItemDao implements ItemDao
{
    const ITEM_DATA = [
        1 => '商品1',
        2 => '商品2',
        3 => '商品3',
    ];
    private $items;
    public function __construct()
    {
        $this->items = [];

        foreach (self::ITEM_DATA as $key => $val) {
            $item = new Item($key, $val);
            $this->items[$item->getId()] = $item;
        }
    }

    public function findById($item_id)
    {
        if (array_key_exists($item_id, $this->items)) {
            return $this->items[$item_id];
        }

        return null;
    }
}

class DbOrderDao implements OrderDao
{
    const ORDER_DATA = [
        1 => '3',
        2 => '1,3',
        3 => '1,2,3',
        4 => '2',
        5 => '2,3',
    ];

    private $orders;
    public function __construct(ItemDao $item_dao)
    {
        $this->orders = [];
        foreach (self::ORDER_DATA as $key => $val) {
            $order = new Order($key);
            foreach (explode(',', $val) as $item_id) {
                $item = $item_dao->findById($item_id);
                if (!is_null($item)) {
                    $order->addItem($item);
                }
            }
            $this->orders[$order->getId()] = $order;
        }
    }

    public function findById($order_id)
    {
        if (array_key_exists($order_id, $this->orders)) {
            return $this->orders[$order_id];
        }

        return null;
    }
}

class MockItemDao implements ItemDao
{
    public function findById($item_id)
    {
        $item = new Item($item_id, 'dummy item');
        return $item;
    }
}

class MockOrderDao implements OrderDao
{
    public function findById($order_id)
    {
        $order = new Order($order_id);
        $order->addItem(new Item('99', 'dummy item 99'));
        $order->addItem(new Item('99', 'dummy item 99'));
        $order->addItem(new Item('98', 'dummy item 98'));

        return $order;
    }
}

class Item
{
    private $id;
    private $name;

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

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

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

class Order
{
    private $id;
    private $items;

    public function __construct($id)
    {
        $this->id = $id;
        $this->items = [];
    }

    public function addItem(Item $item)
    {
        $id = $item->getId();
        if (!array_key_exists($id, $this->items)) {
            $this->items[$id]['object'] = $item;
            $this->items[$id]['amount'] = 0;
        }
        $this->items[$id]['amount']++;
    }

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

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

// // DbFactory
$factory = new DbFactory();
// MockFactory
// $factory = new MockFactory();
$item_id = 1;
$item_dao = $factory->createItemDao();
$item = $item_dao->findById($item_id);
//
echo sprintf("ITEM ID: %d Name: %s\n", $item_id, $item->getName());

$order_id = 3;
$order_dao = $factory->createOrderDao();
$order = $order_dao->findById($order_id);

echo sprintf("ORDER ID: %d\n", $order_id);

foreach ($order->getItems() as $item) {
    echo sprintf("ITEM NAME: %s, AMOUNT: %d\n", $item['object']->getName(), $item['amount']);
}

参考

PHP でデザインパターン (Flyweight)

2018-10-20

Flyweight パターンのメリット

一度インスタンス化したオブジェクトを使い回し、生成されるオブジェクトの数やリソースの消費を抑えます

Flyweightパターンでは、メモリ以外のリソースも節約することができます。たとえば、インスタンスを生成する時間がそうです。インスタンスを生成することは、非常に時間がかかる処理の1つです。

実装例

<?php

class Item
{
    private $code;
    private $name;
    private $price;

    public function __construct($code, $name, $price)
    {
        $this->code = $code;
        $this->name = $name;
        $this->price = $price;
    }

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

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

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

class ItemFactory
{
    const DATA = [
        ['ITEM_CODE_001', 'ITEM_NAME_001', 3800],
        ['ITEM_CODE_002', 'ITEM_NAME_002', 1500],
        ['ITEM_CODE_003', 'ITEM_NAME_003', 800],
    ];

    private $pool;
    private static $instance = null;

    private function __construct()
    {
        $this->buildPool();
    }

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new ItemFactory();
        }

        return self::$instance;
    }

    public function getItem($code)
    {
        if (array_key_exists($code, $this->pool)) {
            return $this->pool[$code];
        }
        return null;
    }

    public function buildPool()
    {
        $this->pool = [];
        foreach (self::DATA as $row) {
            $item = new Item($row[0], $row[1], $row[2]);
            $this->pool[$row[0]] = $item;
        }
    }

    public final function  __clone()
    {
        throw new RuntimeException();
    }
}

$factory = ItemFactory::getInstance();
$items = [];
$items[] = $factory->getItem('ITEM_CODE_001');
$items[] = $factory->getItem('ITEM_CODE_002');
$items[] = $factory->getItem('ITEM_CODE_003');

// true
var_dump($items[0] === $factory->getItem('ITEM_CODE_001'));

// false
var_dump($items[0] === new Item('ITEM_CODE_001', 'ITEM_NAME_001', 3800));

参考

PHPでデザインパターン (Adapter)

2018-10-20

Adapter パターンとは

  • 「すでに提供されているもの」と「必要なもの」の間の「ずれ」を埋めるようなデザインパターン
  • Adapter パターンは、既存のクラスにはまったく手を加えずに、目的のインターフェース(API)にあわせようとするものです。

Adapter パターンには下記の2種類があります

  • クラスによる Adapter パターン (継承を使ったもの)
  • インスタンスによる Adapter パターン (委譲を使ったもの)

実装例

<?php

class ShowFile
{
    private $filename;

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

    public function showPlain()
    {
        echo file_get_contents($this->filename);
    }

    public function showHighlight()
    {
        highlight_file($this->filename);
    }
}

interface DisplaySourceFile
{
    public function display();
}

// $filepath = __FILE__;
// $show_file = new ShowFile($filepath);
// $show_file->showPlain();
// echo "------\n";
// $show_file->showHighlight();

// 継承を使ったパターン
class DisplaySourceFileImpl extends ShowFile implements DisplaySourceFile
{
    public function __construct($filename)
    {
        parent::__construct($filename);
    }

    public function display()
    {
        parent::showHighlight();
    }
}

$show_file = new DisplaySourceFileImpl(__FILE__);
$show_file->display();

// 委譲を使ったパターン
class DisplaySourceFileImpl2 implements DisplaySourceFile
{
    private $show_file;

    public function __construct($filename)
    {
        $this->show_file = new ShowFile($filename);
    }

    public function display()
    {
        $this->show_file->showHighlight();
    }
}

$show_file = new DisplaySourceFileImpl2(__FILE__);
$show_file->display();

所感

既存のクラスに変更を加えたくない時や、既存のクラスを変更できない場合の選択肢の1つになると思います。

参考

PHP でデザインパターン (Iterator)

2018-10-15

Iterator パターンとは

  • リストの内部構造を隠したまま、それぞれの要素にアクセスさせるためのパターン

  • オブジェクトに対する反復操作をおこなうための統一APIを提供するパターン

Iterator パターンのメリット

  • 利用者に、「どの様な構造を持つリストなのか」を意識させないようにできます。

実装例

<?php

class Employee
{
    private $name;
    private $age;
    private $job;

    public function __construct($name, $age, $job)
    {
        $this->name = $name;
        $this->age = $age;
        $this->job = $job;
    }

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

    public function getAge()
    {
        return $this->age;
    }

    public function getJob()
    {
        return $this->job;
    }
}

class Employees implements IteratorAggregate
{
    private $employees;
    public function __construct()
    {
        $this->employees = new ArrayObject();
    }

    public function add(Employee $employee)
    {
        $this->employees[] = $employee;
    }

    public function getIterator()
    {
        return $this->employees->getIterator();
    }
}

$employees = new Employees();
$employees->add(new Employee('SMITH', 32, 'CLERK'));
$employees->add(new Employee('ALLEN', 26, 'SALESMAN'));
$employees->add(new Employee('MARTIN', 50, 'SALESMAN'));
$employees->add(new Employee('CLARK', 45, 'MANAGER'));
$employees->add(new Employee('KING', 58, 'PRESIDENT'));

$iterator = $employees->getIterator();

while  ($iterator->valid()) {
    $employee = $iterator->current();

    printf(
        "%s (%d, %s)\n",
        $employee->getName(),
        $employee->getAge(),
        $employee->getJob()
    );

    $iterator->next();
}

参考

PHP でデザインパターン (Strategy)

2018-10-14

Strategy パターンとは

Strategyパターンでは、アルゴリズムの部分を他の部分と意識的に分離します。

Strategy パターンのメリット

  • 処理毎にまとめることができる
  • 異なる処理を選択するための条件文がなくなる
  • 異なる処理を動的に切り替えることができる

実装例

<?php

abstract class ReadItemDataStrategy
{
    private $filename;
    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    public function getData()
    {
        return $this->readData($this->getFileName());
    }

    public function getFileName()
    {
        return $this->filename;
    }

    protected abstract function readData($filename);
}

class ReadFixedLengthDataStrategy extends ReadItemDataStrategy
{
    protected function readData($filename)
    {
        // MEMO: 実際は読み込みの処理を実装する
        $data = [];
        $data[] = self::class;
        $data[] = sprintf('このファイルを読み込みます: %s', $filename);

        return $data;
    }
}

class ReadTabSeparatedDataStrategy extends ReadItemDataStrategy
{
    protected function readData($filename)
    {
        // MEMO: 実際は読み込みの処理を実装する
        $data = [];
        $data[] = self::class;
        $data[] = sprintf('このファイルを読み込みます: %s', $filename);

        return $data;
    }
}

class ItemDataContext
{
    private $strategy;

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

    public function getItemData()
    {
        return $this->strategy->getData();
    }
}

$strategy1 = new ReadFixedLengthDataStrategy('dummy1.txt');
$context1 = new ItemDataContext($strategy1);
var_dump($context1->getItemData());

echo "\n";

$strategy2 = new ReadTabSeparatedDataStrategy('dummy2.txt');
$context2 = new ItemDataContext($strategy2);
var_dump($context2->getItemData());

参考