Factory MethodパターンのメリットとPHP実装例

## Factory Method パターンとは

Factory Methodパターンは、インスタンスの生成をサブクラスにまかせることで、呼び出し元を生成されるインスタンスの具体的なクラスから解放することを目的としたデザインパターンです。文章ではよくわからなかったので、具体例を挙げます。

通常は次のようにインスタンスを生成します。

```php
$product = new Product();
```

これをインスタンス生成を担うメソッドに置き換えるのがFactoryMethodパターンです。

```php
public function factoryMethod() : Product
{
  return new Product();
}
```

クラス図で表現すると次のようになります。

![スクリーンショット 2020-08-10 11.26.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/500854/076fae27-e573-9bd7-ef52-183677d9c404.png)


これはTemplateMethodパターンの応用だそうです。



## 具体的なクラス名による束縛から解放する

※次の例は自分で考えたものですので、間違っている箇所がありましたらご指摘いただけるとありがたいです

たとえば、スーパークラスに次のようなアルゴリズムがあったとします。

```php
final public function write(string $message): Paper
{
    $papper = new Paper();
    $papper->written($message);

    return $papper;
}
```

紙を用意して、そこにメッセージを書き込んでいます。

ここで問題となるのは、紙以外のもの、たとえばホワイトボードなどにメッセージを書き込みたくなった場合です。

上記の例では`new Paper()`でクラス名を直接指定してインスタンスを生成しているため、このままでは紙以外のものにメッセージを書き込むことはできません。そこで、FactoryMethodパターンではインスタンス生成をサブクラスのメソッドに委ねます。

まずはPaperを抽象化したWritableというスーパークラスを作成します。

```php
abstract class Writable
{
    public abstract function written(string $message): void;
}
```

次は、さきほどの`new Paper()`でインスタンスを生成していた箇所を、メソッドに置き換えます。

```php
abstract class Writer
{
    final public function write(string $message): Writable
    {
        $writable = $this->createWritable();
        $writable->written($message);

        return $writable;
    }

    protected abstract function createWritable(): Writable;
}
```

サブクラスは、次のように実装されます。

```php
class PaperWriter extends Writer
{
    protected function createWritable(): Writable
    {
        return new Paper();
    }
}
```

```php
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を実装しないと、エラーが発生するようになります。

```php
protected abstract function createProduct(string $name): Product;
```

### デフォルトの実装を用意しておく

デフォルトの処理を実装しておくことで、サブクラスで実装されなかった場合もエラーは発生しなくなります。

```php
protected function createProduct(string $name): Product
{
    return new Product($name);
}
```

### デフォルトでは例外を投げる

デフォルトで例外を投げる実装にしておくと、サブクラスでオーバライドしなかった場合に例外が教えてくれます。

```php
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
<?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();
```