【PHPデザインパターン】14_Flyweight～同じものは一度しか作らない

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

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

> [Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Flyweight～同じものは一度しか作らない](http://d.hatena.ne.jp/shimooka/20141217/1418788238)

# 概要
- 一度インスタンス化したオブジェクトを使い回し、生成されるオブジェクトの数やリソースの消費を抑える。
- 環境や状況によって変化しない情報は「intrinsic」（「本質的な」の意）、変化する情報は「extrinsic」（「非本質的な」の意）と呼ばれる。
- Flyweightパターンでは、intrinsicな情報を持つオブジェクトが共有される。

## 構成要素
### Flyweightクラス
- 共有するオブジェクトの共通APIを定義する。

### ConcreteFlyweightクラス
- Flyweightクラスのサブクラス。
- このクラスのインスタンスが共有されるので、intrinsicな情報のみ保持するようにする。

### UnsharedConcreteFlyweightクラス
- Flyweightクラスのサブクラス。
- このクラスのインスタンスは共有されないので、extrinsicな情報を保持しても構わない。

### FlyweightFactoryクラス
- Flyweight型のインスタンスを生成・保持するクラス。

### Clientクラス
- Flyweight型のオブジェクトへの参照を保持するクラス。

## ObjectPoolパターン
- オブジェクトの数を制限して再利用する。
- Flyweightパターンとの共通点が多い。
- GoFパターンではない。

> [Think IT〜Object Poolパターン](https://thinkit.co.jp/article/934/1)

# 実演
## 処理の流れ
- Itemクラスのオブジェクトは、ItemFactoryクラスのメソッドからのみ呼び出せる。
- オブジェクトが存在しない場合のみ、新規にオブジェクトを作成する。
- UnsharedConcreteFlyweightクラスは、今回作成しない。

## ファイル構造
```sh
MyFlyweight
  ├── Item.php
  ├── ItemFactory.php
  └── my_client.php
```

## ソースコード
### Flyweightクラス
兼ConcreteFlyweightクラス。
#### Item.php
```Item.php
<?php
namespace DoYouPhp\PhpDesignPattern\Flyweight\MyFlyweight;

/**
 * FlyweightクラスとConcreteFlyweightクラスに相当する
 */
class Item
{
    private $id;
    private $name;
    private $price;

    // 各プロパティをセットする
    public function __construct($id, $name, $price)
    {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
    }

    // 各要素を返すメソッド
    public function getId()
    {
        return $this->id;
    }

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

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

### FlyweightFactoryクラス
#### ItemFactory.php
```ItemFactory.php
<?php
namespace DoYouPhp\PhpDesignPattern\Flyweight\MyFlyweight;

use DoYouPhp\PhpDesignPattern\Flyweight\MyFlyweight\Item;

/**
 * FlyweightFactoryクラスに相当する
 * Singletonパターンにもなっている
 */
class ItemFactory
{
    private $pool;
    private static $instance = null;

    // コンストラクタ
    // 配列型のテスト用データからItemクラスのインスタンスを作成する
    private function __construct($data_array)
    {
        $this->buildPool($data_array);
    }

    // ItemFactoryクラスのインスタンスを返す
    // 同時にItemクラスのインスタンスが作成される
    // ItemFactoryクラスのインスタンスは、このメソッドからのみ返される
    public static function getInstance($data_array)
    {
        if (is_null(self::$instance)) {
            self::$instance = new ItemFactory($data_array);
        }

        return self::$instance;
    }

    // 引数のidに該当するItemクラスのインスタンスを返す
    public function getItem($id)
    {
        if (array_key_exists($id, $this->pool)) {
            return $this->pool[$id];
        } else {
            return;
        }
    }

    // 配列型のテスト用データを読み込み、Itemクラスのインスタンスを作成する
    // $poolはItemクラスのインスタンスを要素とする配列
    private function buildPool($data_array)
    {
        $this->pool = array();

        foreach ($data_array as $data) {
            $item_id   = $data['id'];
            $item_name = $data['name'];
            $price     = $data['price'];

            $this->pool[$item_id] = new Item($item_id, $item_name, $price);
        }
    }

    // インスタンスの複製を防ぐ
    final public function __clone()
    {
        throw new \RuntimeException(get_class($this).'クラスのインスタンスは複製できません');
    }
}
```

### Clientクラス
#### my_client.php
```my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Flyweight\MyFlyweight;

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

use DoYouPhp\PhpDesignPattern\Flyweight\MyFlyweight\ItemFactory;

// id,name,priceを表示する
function dumpData($data)
{
    foreach ($data as $object) {
        echo 'id:'.$object->getId().',name:'.$object->getName().',price:'.$object->getPrice().'<br>'."\n";
    }
}

// 配列型のテスト用データ
$data_array = array(
    array('id' => 1, 'name' => apple,  'price' => 100),
    array('id' => 2, 'name' => orange, 'price' => 80),
    array('id' => 3, 'name' => banana, 'price' => 130)
);

// ItemFactoryクラスのインスタンス作成と呼び出しを行う
$factory = ItemFactory::getInstance($data_array);

// idを引数にして、対応するItemクラスのインスタンスを取得する
// 配列itemsの要素としてセットする
$items = array();
$items[] = $factory->getItem(1);
$items[] = $factory->getItem(2);
$items[] = $factory->getItem(3);

// idが同じであれば、既存のオブジェクトと同じものである
if ($items[0] === $factory->getItem(1)) {
    echo '同一のオブジェクトです'.'<br>'."\n";
} else {
    echo '同一のオブジェクトではありません'.'<br>'."\n";
}

// 取得したデータのプロパティを出力する
dumpData($items);

// ItemFactoryクラスのオブジェクトは複製できないことを確認する
try {
    $factory_clone = clone $factory;
} catch (\RuntimeException $e) {
    echo $e->getMessage().'<br>'."\n";
}
```