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


引用記事

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

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

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Flyweight~同じものは一度しか作らない

概要

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

構成要素

Flyweightクラス

  • 共有するオブジェクトの共通APIを定義する。

ConcreteFlyweightクラス

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

UnsharedConcreteFlyweightクラス

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

FlyweightFactoryクラス

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

Clientクラス

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

ObjectPoolパターン

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

Think IT〜Object Poolパターン

実演

処理の流れ

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

ファイル構造

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";
}