【PHPデザインパターン】19_Prototype～コピーして作る

# 引用記事
この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

> [Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Prototype～コピーして作る](http://d.hatena.ne.jp/shimooka/20141218/1418888728)

# 概要
- 「prototype」は「試作品」「原型」という意味。
- オブジェクトをコピーして、新しいオブジェクトを生成する。
- オブジェクトの生成処理を隠蔽し、クラス同士の関係をゆるくできる。

## 構成要素
### Prototypeクラス
- コピーするためのメソッドを定義する親クラス。

### ConcretePrototypeクラス
- Prototypeクラスのサブクラス。

### Clientクラス
- Prototype型のオブジェクトを利用して、新しいインスタンスを生成する。

# 実演
## 処理の流れ
- ``__cloneメソッド``を使用し、``ItemPrototype型オブジェクト``をコピーする。
- ItemPrototype型オブジェクト内部では、stdClass型オブジェクトへの参照を行なっている。
- __cloneメソッドの場合、オブジェクトの参照はコピーできないため「浅いコピー」となる。
- オブジェクトの参照をコピーする場合は、``ItemPrototypeクラス``のサブクラスで__cloneメソッドを実装する必要がある。
- 今回は「浅いコピー」を行う``ShallowCopyItemクラス``と、「深いコピー」を行う``DeepCopyItemクラス``を定義する。
- クライアントから新しいインスタンスを要求された時は、``ItemManagerクラス``でオブジェクトをコピーする。

## ファイル構造
```sh
MyPrototype
  ├── DeepCopyItem.php
  ├── ItemManager.php
  ├── ItemPrototype.php
  ├── ShallowCopyItem.php
  └── my_client.php
```

## ソースコード
### Prototypeクラス
#### ItemPrototype.php
```ItemPrototype.php
<?php
namespace DoYouPhp\PhpDesignPattern\Prototype\MyPrototype;

/**
 * Prototypeクラスに相当する
 * 複製するためのメソッドを定義する
 */
abstract class ItemPrototype
{
    // ItemPrototype型オブジェクトのプロパティ
    private $item_code;
    private $item_name;
    // 定義済みクラスであるstdClass型オブジェクトをセットする
    private $detail;

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

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

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

    // $detailのみ、stdClass型のオブジェクトとする
    // ItemPrototype型オブジェクトが内包している形になる
    public function setDetail(\stdClass $detail)
    {
        $this->detail = $detail;
    }

    public function getDetail()
    {
        return $this->detail;
    }

    // 各プロパティを出力する
    public function dumpData()
    {
        echo '商品名：'.$this->getName().'<br>'."\n";
        echo '商品番号：'.$this->getCode().'<br>'."\n";
        echo 'コメント：'.$this->detail->comment.'<br>'."\n";
    }

    // cloneを使ってインスタンスを複製する
    // 実装はサブクラスで行う
    public function newInstance()
    {
        $new_instance = clone $this;

        return $new_instance;
    }

    // protectedキーワードで、このクラスとサブクラス以外はメソッドを呼び出せないようにする
    // 外部から直接cloneされることはない
    abstract protected function __clone();
}
```

### ConcretePrototypeクラス
#### DeepCopyItem.php
```DeepCopyItem.php
<?php
namespace DoYouPhp\PhpDesignPattern\Prototype\MyPrototype;

use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\ItemPrototype;

/**
 * ConcretePrototypeクラスに相当する
 */
class DeepCopyItem extends ItemPrototype
{
    // 深いコピーを行うための実装
    // 内包しているstdClass型オブジェクトも複製する
    protected function __clone()
    {
        $this->setDetail(clone $this->getDetail());
    }
}
```

#### ShallowCopyItem.php
```ShallowCopyItem.php
<?php
namespace DoYouPhp\PhpDesignPattern\Prototype\MyPrototype;

use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\ItemPrototype;

/**
 * ConcretePrototypeクラスに相当する
 */
class ShallowCopyItem extends ItemPrototype
{
    // 浅いコピーを行うので、空の実装を行う
    protected function __clone()
    {
    }
}
```


### Clientクラス
#### ItemManager.php
```ItemManager.php
<?php
namespace DoYouPhp\PhpDesignPattern\Prototype\MyPrototype;

use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\ItemPrototype;

/**
 * Clientクラスに相当する
 * ItemPrototype型オブジェクトを利用して、新しいインスタンスを生成する
 */
class ItemManager
{
    private $items;

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

    // $item_codeをキーとする配列に、ItemPrototype型インスタンスをセットする
    public function registItem(ItemPrototype $item)
    {
        $this->items[$item->getCode()] = $item;
    }

    // インスタンスを複製する
    public function create($item_code)
    {
        // 指定した$item_codeを持つインスタンスが配列に存在しない場合は、例外を投げる
        // 存在する場合は、インスタンスの複製を行う
        if (!array_key_exists($item_code, $this->items)) {
            throw new \Exception('コード'.$item_code.'の商品は存在しません');
        }

        $cloned_item = $this->items[$item_code]->newInstance();

        return $cloned_item;
    }
}
```

### Client
#### my_client.php
```my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Prototype\MyPrototype;

require dirname(dirname(__DIR__)).'/vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\ItemManager;
use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\DeepCopyItem;
use DoYouPhp\PhpDesignPattern\Prototype\MyPrototype\ShallowCopyItem;

// オブジェクト複製後、内包しているオブジェクトを変更して出力するメソッド
function testCopy(ItemManager $manager, $item_code)
{
    // コピー用のメソッド
    // 同型のインスタンスを2つ生成して比較する
    $item1 = $manager->create($item_code);
    $item2 = $manager->create($item_code);

    // 内包しているstdClass型オブジェクトのプロパティを変更する
    $item2->getDetail()->comment = '書き換えたコメント';

    // 出力する
    echo '----- オリジナル -----'.'<br>'."\n";
    $item1->dumpData();
    echo '----- コピー -----'.'<br>'."\n";
    $item2->dumpData();
}

$manager = new ItemManager();

// 商品データの作成
// ItemPrototype型オブジェクトを生成する
// オブジェクトの中で、stdClass型オブジェクトへの参照を内包している

// 深いコピーを行う
// 各オブジェクトは複製された個々の$detailを参照する
// そのため、コピー後にstdClass型オブジェクトのプロパティを変更した場合、他の同型オブジェクトに影響はない
$item = new DeepCopyItem(1, 'apple');
$detail = new \stdClass();
$detail->comment = 'appleテストコメント';
$item->setDetail($detail);
$manager->registItem($item);

// 浅いコピーを行う
// $detail自身は複製されないので、各オブジェクトは共通の$detailを参照する
// そのため、コピー後にstdClass型オブジェクトのプロパティを変更した場合、他の同型オブジェクトに影響がある
$item = new ShallowCopyItem(2, 'orange');
$detail = new \stdClass();
$detail->comment = 'orangeテストコメント';
$item->setDetail($detail);
$manager->registItem($item);

// オブジェクトの参照の変化を確認する
try {
    echo '<深いコピー>'.'<br>'."\n";
    testCopy($manager, 1);

    echo '<浅いコピー>'.'<br>'."\n";
    testCopy($manager, 2);

    testCopy($manager, 3);
} catch (\Exception $e) {
    echo '<例外>'.'<br>'."\n";
    echo $e->getMessage();
}
```