Factory Method パターンとは
Factory Methodパターンは、インスタンスの生成をサブクラスにまかせることで、呼び出し元を生成されるインスタンスの具体的なクラスから解放することを目的としたデザインパターンです。文章ではよくわからなかったので、具体例を挙げます。
通常は次のようにインスタンスを生成します。
$product = new Product();
これをインスタンス生成を担うメソッドに置き換えるのがFactoryMethodパターンです。
public function factoryMethod() : Product
{
return new Product();
}
クラス図で表現すると次のようになります。
これはTemplateMethodパターンの応用だそうです。
具体的なクラス名による束縛から解放する
※次の例は自分で考えたものですので、間違っている箇所がありましたらご指摘いただけるとありがたいです
たとえば、スーパークラスに次のようなアルゴリズムがあったとします。
final public function write(string $message): Paper
{
$papper = new Paper();
$papper->written($message);
return $papper;
}
紙を用意して、そこにメッセージを書き込んでいます。
ここで問題となるのは、紙以外のもの、たとえばホワイトボードなどにメッセージを書き込みたくなった場合です。
上記の例ではnew Paper()
でクラス名を直接指定してインスタンスを生成しているため、このままでは紙以外のものにメッセージを書き込むことはできません。そこで、FactoryMethodパターンではインスタンス生成をサブクラスのメソッドに委ねます。
まずはPaperを抽象化したWritableというスーパークラスを作成します。
abstract class Writable
{
public abstract function written(string $message): void;
}
次は、さきほどのnew Paper()
でインスタンスを生成していた箇所を、メソッドに置き換えます。
abstract class Writer
{
final public function write(string $message): Writable
{
$writable = $this->createWritable();
$writable->written($message);
return $writable;
}
protected abstract function createWritable(): Writable;
}
サブクラスは、次のように実装されます。
class PaperWriter extends Writer
{
protected function createWritable(): Writable
{
return new Paper();
}
}
class Paper extends Writable
{
private $message;
public function written(string $message): void
{
$this->message = $message;
echo '紙に' . $message . 'と書き込まれました' . "\n";
}
public function show(): void
{
echo '紙には' . $this->message . 'と書かれています' . "\n";
}
}
このように、サブクラスにcreateWritable()
実装を委ねることで、作者が自由に書き込むものを選ぶことができるようになります。これで、紙ではなくホワイトボードを使用したいという作者が現れても、write()
のアルゴリズムを修正する必要がなくなりました。
ポイントは、Writerクラスのwrite()
の中から具体的なクラス名が消えたということのようです。これを具体的なクラス名による束縛から解放されたと表現するようです。
Factory Methodの実装方法
タイトルにあるFactory Methodとは、どのメソッドのことなのか、少し迷いました。
どうやら、インスタンスを生成するための抽象メソッドのことをFactory Methodと呼ぶようです。さきほどの例でいうとWriterクラスのcreateWritable()
がFactory Methodにあたると思います。そして、おそらくWriterクラスのwrite()
はTemplate Methodだと思います。
Factory Methodは、サブクラスで実装されることを期待していますが、次の3通りの記述方法があるようです。
- 抽象メソッドにする
- デフォルトの実装を用意しておく
- デフォルトでは例外を投げる
抽象メソッドにする
抽象メソッドにすることで、サブクラスはFactory Methodを実装しないと、エラーが発生するようになります。
protected abstract function createProduct(string $name): Product;
デフォルトの実装を用意しておく
デフォルトの処理を実装しておくことで、サブクラスで実装されなかった場合もエラーは発生しなくなります。
protected function createProduct(string $name): Product
{
return new Product($name);
}
デフォルトでは例外を投げる
デフォルトで例外を投げる実装にしておくと、サブクラスでオーバライドしなかった場合に例外が教えてくれます。
protected function createProduct(string $name): Product
{
throw new FactoryMethodRuntimeException(); // 例外クラスは別途作成
}
Factory Methodパターンのメリット
Factory Methodパターンを使うメリットをまとめてみます。
- 呼び出し元はオブジェクトの生成手順や分岐を気にする必要がなくなる
- 新しいオブジェクトを追加する場合も呼び出し元を修正する必要がない
自分の理解では、Factory Methodを実装すると必然的にTemplate Methodが作られるので、Factory MethodパターンはTemplate Methodパターンの応用といわれるのだと思います。
Factory MethodパターンPHP実装例
<?php
abstract class Writable
{
public abstract function written(string $message): void;
public abstract function show(): void;
}
abstract class Writer
{
final public function write(string $message): Writable
{
$writable = $this->createWritable();
$writable->written($message);
return $writable;
}
protected abstract function createWritable(): Writable;
}
class Paper extends Writable
{
private $message;
public function written(string $message): void
{
$this->message = $message;
echo '紙に' . $message . 'と書き込まれました' . "\n";
}
public function show(): void
{
echo '紙には' . $this->message . 'と書かれています' . "\n";
}
}
class PaperWriter extends Writer
{
protected function createWritable(): Writable
{
return new Paper();
}
}
function main(): void
{
$writer = new PaperWriter();
$writable1 = $writer->write('一昨日の記録');
$writable2 = $writer->write('昨日の記録');
$writable3 = $writer->write('今日の記録');
$writable1->show();
$writable2->show();
$writable3->show();
}
main();