🏭

FactoryMethodパターンを使ってみる

2023/04/02に公開

FactoryMethodパターンとは

Factory Methodパターンは、オブジェクト生成の責任をサブクラスに委譲するデザインパターンです。

オブジェクトの生成をカプセル化することで依存度を下げることができます。

抽象クラス(親クラス)に抽象メソッドとして、オブジェクトの生成を定義しますが、具体的にどのクラスをインスタンス化するかは子クラスが決定します

そのため、親クラスは子クラスが作成したオブジェクトを利用しますが、具体的なオブジェクトについては知らなくて済み、具体的なオブジェクトに依存しなくなります。

FactoryMethodパターンのクラス図

環境

参考

https://www.oreilly.co.jp/books/9784873119762/

今回の実装について

  • 地域ごとにフランチャイズ展開されるピザショップを想定
  • 注文された後の工程は基本的に同一
  • 地域ごとにピザの内容は異なる

クラス図

クラス名 説明
PizzaStore 抽象クラス。ピザの注文処理とピザの生成メソッドを持つ。
NYPizzaStore ニューヨークスタイルのピザを生成する具象クラス。
具体的なピザオブジェクトの生成を行う。
ChicagoPizzaStore シカゴスタイルのピザを生成する具象クラス。
具体的なピザオブジェクトの生成を行う。
Pizza 抽象クラス。ピザの基本情報と調理、焼く、カット、箱詰めするメソッドを持つ。
NYCheesePizza ニューヨークスタイルのチーズピザを表現する具象クラス。
NYStyleClamPizza ニューヨークスタイルのクラムピザを表現する具象クラス。
ChicagoCheesePizza シカゴスタイルのチーズピザを表現する具象クラス。
ChicagoClamPizza シカゴスタイルのクラムピザを表現する具象クラス。

①PizzaStore

抽象クラス(親クラス)として、PizzaStoreクラスを実装します。
ピザの注文処理とピザの生成を抽象メソッドとして持ちます。
具体的なピザオブジェクトについて知る必要がなく、抽象ピザクラスにのみ依存します。

PizzaStore.php
abstract class PizzaStore
{
    /**
     * ピザ注文処理
     * ピザの種類に関係せず、変化しない部分
     *
     * @param string $type
     * @return Pizza
     */
    public function orderPizza(string $type): Pizza
    {
        $pizza = $this->createPizza($type);

        $pizza->prepare();
        $pizza->bake();
        $pizza->cut();
        $pizza->box();

        return $pizza;
    }

    /**
     * ピザ作成
     * 具体的な処理はサブクラスごとに異なる
     *
     * @param string $type
     * @return Pizza|null
     */
    abstract public function createPizza(string $type): ?Pizza;
}

②Pizza

具体的なピザの抽象クラスとしてPizzaクラスを実装します。
基本情報と調理、焼く、カット、箱詰めするメソッドを持ちます。

Pizza.php
abstract class Pizza
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var string
     */
    public string $dough;

    /**
     * @var string
     */
    public string $sauce;

    /**
     * @var array
     */
    public array $toppings;

    public function prepare(): void
    {
        echo "{$this->name}を準備する\n";
        echo "生地をこねる...\n";
        echo "ソースを追加...\n";
        echo "トッピングを追加...\n";
        foreach ($this->toppings as $topping) {
            echo "- {$topping}\n";
        }
    }

    public function bake(): void
    {
        echo "{$this->name}を焼く\n";
    }

    public function cut(): void
    {
        echo "{$this->name}をカットする\n";
    }

    public function box(): void
    {
        echo "{$this->name}を箱に入れる\n";
    }
}

③NYPizzaStore

ニューヨーク店を実装します。
PizzaStoreを継承し、具象クラスのインスタンス化を担います。

NYPizzaStore.php
class NYPizzaStore extends PizzaStore
{
    /**
     * ニューヨークスタイルのピザをインスタンス化
     *
     * @param string $type
     * @return Pizza|null
     */
    public function createPizza(string $type): ?Pizza
    {
        // ピザインスタンスの生成
        $pizza = null;
        if ($type === 'チーズ') {
            $pizza = new NYCheesePizza();
        } elseif ($type === 'クラム') {
            $pizza = new NYClamPizza();
        }

        return $pizza;
    }
}

④ニューヨークスタイルの具象クラス

ニューヨークスタイルの具象ピザクラスを実装します。

NYCheesePizza.php
class NYCheesePizza extends Pizza
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var string
     */
    public string $dough;

    /**
     * @var string
     */
    public string $sauce;

    /**
     * @var array
     */
    public array $toppings;

    public function __construct()
    {
        $this->name = 'チーズピザ(ニューヨークスタイル)';
        $this->dough = '薄いクラスト生地';
        $this->sauce = 'ニューヨーク用ソース';
        $this->toppings = ['ゴルゴンゾーラチーズ', 'オリーブオイル'];
    }
}
NYClamPizza.php
class NYClamPizza extends Pizza
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var string
     */
    public string $dough;

    /**
     * @var string
     */
    public string $sauce;

    /**
     * @var array
     */
    public array $toppings;

    public function __construct()
    {
        $this->name = 'クラムピザ(ニューヨークスタイル)';
        $this->dough = '薄いクラスト生地';
        $this->sauce = 'ニューヨーク用ソース';
        $this->toppings = ['貝'];
    }
}

⑤ChicagoPizzaStore

シカゴ店を実装します。
ニューヨーク店と同様にPizzaStoreを継承し、具象クラスのインスタンス化を担います。

ChicagoPizzaStore.php
class ChicagoPizzaStore extends PizzaStore
{
    /**
     * シカゴスタイルのピザをインスタンス化する
     *
     * @param string $type
     * @return Pizza|null
     */
    public function createPizza(string $type): ?Pizza
    {
        $pizza = null;

        if ($type === 'チーズ') {
            $pizza = new ChicagoCheesePizza();
        } elseif ($type === 'クラム') {
            $pizza = new ChicagoClamPizza();
        }

        return $pizza;
    }
}

⑥シカゴスタイルの具象ピザクラス

シカゴスタイルの具象ピザクラスを実装します。
シカゴスタイルの場合、別のカット方法のためcutメソッドをオーバーライドします。

ChicagoCheesePizza.php
class ChicagoCheesePizza extends Pizza
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var string
     */
    public string $dough;

    /**
     * @var string
     */
    public string $sauce;

    /**
     * @var array
     */
    public array $toppings;

    public function __construct()
    {
        $this->name = 'チーズピザ(シカゴスタイル)';
        $this->dough = '極厚クラスト生地';
        $this->sauce = 'シカゴ用ソース';
        $this->toppings = ['モツァレラチーズ', 'はちみつ'];
    }

    public function cut(): void
    {
        echo "{$this->name}を四角形にカットする\n";
    }
}
ChicagoClamPizza.php
class ChicagoClamPizza extends Pizza
{
    /**
     * @var string
     */
    public string $name;

    /**
     * @var string
     */
    public string $dough;

    /**
     * @var string
     */
    public string $sauce;

    /**
     * @var array
     */
    public array $toppings;

    public function __construct()
    {
        $this->name = 'クラムピザ(シカゴスタイル)';
        $this->dough = '極厚クラスと生地';
        $this->sauce = 'シカゴ用ソース';
        $this->toppings = ['新鮮な貝'];
    }

    public function cut(): void
    {
        echo "{$this->name}を四角形にカットする\n";
    }
}

⑦クライアントコード

上記の実装を利用するクライアントコードは以下になります。

  // ニューヨーク
  $nyStore = new NYPizzaStore();
  $nyStore->orderPizza('チーズ');
  $nyStore->orderPizza('クラム');

  // シカゴ
  $chicagoStore = new ChicagoPizzaStore();
  $chicagoStore->orderPizza('チーズ');
  $chicagoStore->orderPizza('クラム');

まとめ

このようにFactoryMethodパターンを使うことで、店舗ごとでピザの作り方やカット方法が異なっても柔軟に対応することができています。
さらに、店舗やピザの種類が増えるなどの変更・拡張が簡単に行えます。

少しでもFactoryMethodパターンの有用性が伝われば嬉しいです!
最後までお読みいただきありがとうございました!

Discussion