『Java言語で学ぶデザインパターン入門』をPHPで実習する 第4章Factory Method

増補改訂版Java言語で学ぶデザインパターン入門

本記事に掲載したサンプルコードは、https://github.com/ryo-utsunomiya/design_patternでも公開中です。

Factory Methodとは

Factory Methodとは、インスタンスの作り方の大枠をスーパークラスのFactoryで定め、具体的な肉付けはサブクラスのFactoryで行う、というインスタンス生成のパターンです。Template Methodと似ていますが、インスタンスの生成に特化している点が異なります。

まず、「生成するもの(Factory)」と、「生成されるもの(Product)」を表す、抽象的な枠組み(framework)を作成します。

<?php

namespace framework;

abstract class Factory
{
    /**
     * @param string $owner
     * @return Product
     */
    final public function create($owner)
    {
        $product = $this->createProduct($owner);
        $this->registerProduct($product);

        return $product;
    }

    /**
     * @param string $owner
     * @return Product
     */
    abstract protected function createProduct($owner);

    /**
     * @param Product $product
     */
    abstract protected function registerProduct(Product $product);
}

Factroyには、createというpublicメソッドがあります。このメソッドがProductインスタンスの作成を扱います。

<?php

namespace framework;

abstract class Product
{
    abstract public function useThis();
}

Productは、Factoryによって作られるインスタンスで、useThisというメソッドを持っています(useはPHPでは予約語なので、メソッド名には使えません)。

次に、IDカードを例とした、具体的なFactoryとProductを作成します。

<?php

namespace idcard;

use frameworkFactory;
use frameworkProduct;

class IDCardFactory extends Factory
{
    /**
     * @var array
     */
    private $database = [];

    /**
     * @var int
     */
    private $serial = 100;

    /**
     * @param string $owner
     * @return IDCard
     */
    protected function createProduct($owner)
    {
        return new IDCard($owner, $this->serial++);
    }

    /**
     * @param Product $product
     */
    protected function registerProduct(Product $product)
    {
        if (!$product instanceof IDCard) {
            throw new InvalidArgumentException();
        }
        $this->database[$product->getSerial()] = $product->getOwner();
    }
}

PHPのタイプヒント機能では、「Product $product」と定義した場合、「$productはProductか、Productを継承したクラスであること」という制約になります。

しかし、IDCardFactory::registerProduct()は$productがgetSerialとgetOwnerというメソッドを持つことを期待しているので、実際はProductではなくIDCardを渡して欲しい。registerProductのシグネチャをregisterProduct(IDCard $product)と変えてしまうと親クラスとは異なるシグネチャでのオーバーライドでエラーになるので、Productのタイプヒントは残した上でinstanceofでIDCardであることを確認しています。

<?php

namespace idcard;

use frameworkProduct;

class IDCard extends Product
{
    /**
     * @var string
     */
    private $owner;

    /**
     * @var int
     */
    private $serial;

    /**
     * @param $owner
     * @param $serial
     */
    public function __construct($owner, $serial)
    {
        if (!is_string($owner) || !ctype_digit((string)$serial)) {
            throw new InvalidArgumentException();
        }

        printf("Making %s's card(No:%d).n", $owner, $serial);
        $this->owner  = $owner;
        $this->serial = $serial;
    }

    public function useThis()
    {
        printf("Use %s's card(No:%d).n", $this->owner, $this->serial);
    }

    /**
     * @return string
     */
    public function getOwner()
    {
        return $this->owner;
    }

    /**
     * @return int
     */
    public function getSerial()
    {
        return $this->serial;
    }
}

Productを継承するIDCardオブジェクトは上のような実装になります。

実行コードは以下のようになります。

<?php

require_once __DIR__ . '/../autoload.php';

$factory = new idcardIDCardFactory();
$card1 = $factory->create('Stan');
$card2 = $factory->create('Kyle');
$card3 = $factory->create('Cartman');
$card4 = $factory->create('Kenny');
$card1->useThis();
$card2->useThis();
$card3->useThis();
$card4->useThis();

コメントを残す

コメントを残す