PHP5でデザインパターン − FactoryMethodパターン

今日のお題はFactoryMethodパターン。オブジェクトを生成する際に、製品(Product)と工場(Factory)を分離します。Productのインスタンス生成をクライアントではなく、Factoryにカプセル化することで拡張性と柔軟性を持った設計にします。クラス図的にはこんな感じでいいのかな?

http://studio-m.heteml.jp/pattern/factory/pizzahouse/class.gif

今回は『HeadFirstデザインパターン』のサンプルをそのまま実装する形にしています。スクリプトはピザクラスと、それを生成するファクトリクラスで構成されます。

まずは、Productとなるピザの抽象クラスとそれを実装するサブクラス(NYスタイルのチーズピザとシカゴスタイルのチーズピザ)。

Pizza.php

<?php
/**
 * FactoryMethodパターンによるPizza
 */

/**
 * Pizza
 */
abstract class Pizza {
    protected $name;
    protected $dough;
    protected $sauce;
    protected $toppings;

    public function prepare()
    {
        echo "--- ".$this->name."を作っています ---<br>\n";
        echo "<ol><li>".$this->name."を下処理\n";
        echo "<ul><li>生地をこねる…".$this->dough."</li>\n";
        echo "<li>ソースを追加…".$this->sauce."</li>\n";
        echo "<li>トッピングを追加:<br>\n";
        foreach ($this->toppings as $topping) {
            echo " ".$topping."<br>\n";
        }
        echo "</li></ul>\n";
    }

    public function bake()
    {
        echo "<li>350度で25分間焼く</li>\n";
    }

    public function cut()
    {
        echo "<li>ピザを扇形に切り分ける</li>\n";
    }

    public function box()
    {
        echo "<li>PizzaStoreの正式な箱にピザを入れる</li></ol>\n";
    }

    public function getName()
    {
        return $this->name;
    }
}

/**
 * NYStyleCheesePizza
 */
class NYStyleCheesePizza extends Pizza {
    public function __construct()
    {
        $this->name = 'ニューヨークスタイルのソース&チーズピザ';
        $this->dough = '薄いクラスト生地';
        $this->sauce = 'マリナラソース';
        $this->toppings[] = '粉レッジャーノチーズ';
    }
}

/**
 * ChicagoStyleCheesePizza
 */
class ChicagoStyleCheesePizza extends Pizza {
    public function __construct()
    {
        $this->name = 'シカゴスタイルのディープディッシュチーズピザ';
        $this->dough = '極厚クラスト生地';
        $this->sauce = 'プラムトマトソース';
        $this->toppings[] = '刻んだモツァレラチーズ';
    }

    public function cut()
    {
        echo "<li>ピザを四角形に切り分ける</li>\n";
    }

}

次に、Factoryとなるピザ店の抽象クラスとそれを実装するサブクラス(NYスタイルのピザ店とシカゴスタイルのピザ店)。どちらもメニューはチーズピザしかありません。

PizzaStore.php

<?php
/**
 * FactoryMethodパターンによるPizzaStore
 */

/**
 * PizzaStore
 */
abstract class PizzaStore {
    public function orderPizza($type)
    {
        $pizza = $this->createPizza($type);
        $pizza->prepare();
        $pizza->bake();
        $pizza->cut();
        $pizza->box();
        return $pizza;
    }

     abstract protected function createPizza($type);
}

/**
 * NYPizzaStore
 */
class NYPizzaStore extends PizzaStore {
    protected function createPizza($item)
    {
        switch ($item) {
          case 'チーズ':
            return new NYStyleCheesePizza();
            break;
        }
    }
}

/**
 * ChicagoPizzaStore
 */
class ChicagoPizzaStore extends PizzaStore {
    protected function createPizza($item)
    {
        switch ($item) {
          case 'チーズ':
            return new ChicagoStyleCheesePizza();
            break;
        }
    }
}

そして、クライアントとなるテストスクリプト

PizzaTest.php

<?php
/**
 * FactoryMethodパターンのテスト
 */

require_once 'Pizza.php';
require_once 'PizzaStore.php';

$nyStore = new NYPizzaStore();
$chicagoStore = new ChicagoPizzaStore();

$pizza = $nyStore->orderPizza('チーズ');
echo 'イーサンの注文は'.$pizza->getName()."<br><br>\n";

$pizza = $chicagoStore->orderPizza('チーズ');
echo 'ジョエルの注文は'.$pizza->getName()."<br><br>\n";

実際の動作結果はこちら

この程度のコードであればFactoryの実装をPizzaクラスやクライアントに盛り込んでしまっても問題ありませんが、拡張性がまるで違います。ピザの種類を増やすには新たなピザのクラスを作成し、それを取り扱うピザ店のcreatePizzaメソッドでインスタンス化します。ピザの材料が変わる場合は、そのピザクラスだけの修正で済みます。ピザ店を増やすには、新たなピザ店のクラスを作成します。この設計は、ピザとピザ店どちらの拡張にも耐えられる構造を持っています。

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

この本の日本語訳がいかにも「英語を訳しました」的な雰囲気なので、つられて妙な日本語になってきました。