本記事に掲載したサンプルコードは、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();