Web事業部実績紹介
Web事業部実績紹介
2015.02.26

【PHPで学ぶデザインパターン入門】第5回 Factoryパターン

こんにちは、王です。
【PHPで学ぶデザインパターン】第5回はFactoryパターンのご紹介となります。

Factoryパターンの考え方は実に簡単です。「クラスのインスタンス化業務を肩代わりしてくれるオブジェクト」を導入していれば「Factoryパターン」と言っていいのです。

要はクラスのインスタンス化を行う時に、自分でnew TheClass()するのではなく、factory.create('the_class')のように、「誰か」に代わりにやってもらうのです。その「誰か」が「Factory」です。いわば、オブジェクトを作るオブジェクトですね。「オブジェクトの製造所」 → 「Factory」というわけです。

インスタンス化で複雑なオプションが必要なときに活かせるパターンです。逆に言えば、インスタンス化がさほど難しくない場合には適用すべきではありません。必要以上にアプリケーションが複雑化するからです。

Simple Factory パターン

Factoryパターンの簡易版であるSimple Factoryを先に見ていきましょう。
ピザのオンラインストアを例に考えていきたいと思います。

PizzaStoreクラスには「order_pizza」というメソッドがあり、ピザの種類を文字列で渡せば、ピザを注文することができます。

pizza_UML

order_pizzaの実装は下記のコードで実現できるでしょう。

public function order_pizza( $type ) {
	$pizza = null;
	switch ( $type ) {
		case 'cheese':
			$pizza = new CheesePizza();
			break;
		case 'clam':
			$pizza = new ClamPizza();
			break;
		case 'veggie':
			$pizza = new VeggiePizza();
			break;
	}
	
	$pizza->prepare();
	$pizza->bake();
	$pizza->cut();
	$pizza->box();
	
	return $pizza;
}

 
将来的に新しい種類のピザを追加したいときや、ある種類のピザをメニューから消したいとき、switchの中身を編集しますよね。しかし、このやり方ではOCPSRPに違反してしまいます。
そのため、これから先コードが変わりそうな「ピザを作る」部分は、order_pizzaから切り離して他のクラスに依頼するのが望ましく、その依頼先のクラスが「Factory(SimplePizzaFactory)」クラスとなります。

「Factory」クラスの仕事はもっぱら「Pizza」を生産することです。この「工場」は自社で持っていないため、いつでも別の違う工場に簡単に乗り換えることができ、全く違うピザメニューを簡単に作ることができるというメリットもあります。

新しい構成をUML図で表すと下の図のようになります。

pizza_UML_2

実装コード

// 工場(SimplePizzaFactory)が作っている商品(Pizza)
abstract class Pizza {
	protected $name, $dough, $sauce;
	protected $toppings = array();

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

	public function prepare() {
		echo 'Preparing ' . $this->name . '<br>';
	}

	public function bake() {
		echo 'Baking ' . $this->name . '<br>';
	}

	public function cut() {
		echo 'Cutting ' . $this->name . '<br>';
	}

	public function box() {
		echo 'Boxing ' . $this->name . '<br>';
	}

	public function describe() {
		echo '---' . $this->name . '---<br>';
		echo 'Dough: ' . $this->dough . '<br>';
		echo 'Sauce: ' . $this->sauce . '<br>';
		echo 'Topping: <br>';
		foreach ( $this->toppings as $topping ) {
			echo ' --' . $topping . '<br>';
		}
		echo '<br><br>';
	}
}

//チーズピザ
class CheesePizza extends Pizza {

	function __construct() {
		$this->name       = 'Cheese Pizza';
		$this->dough      = 'Regular Crust';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Fresh Mozzarella';
		$this->toppings[] = 'Parmesan';
	}
}

//ハマグリピザ
class ClamPizza extends Pizza {

	function __construct() {
		$this->name       = 'Clam Pizza';
		$this->dough      = 'Regular Crust';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Grated parmesan';
		$this->toppings[] = 'Diced onion';
	}
}

//野菜ピザ
class VeggiePizza extends Pizza {

	function __construct() {
		$this->name       = 'Veggie Pizza';
		$this->dough      = 'Crust';
		$this->sauce      = 'Marinara Sauce';
		$this->toppings[] = 'Shredded mozzarella';
		$this->toppings[] = 'Sliced mushrooms';
		$this->toppings[] = 'Sliced red pepper';
	}
}

// 工場(Factory)クラス
class SimplePizzaFactory {
	/**
	 * The Factory-Method
	 *
	 * @param string $type
	 *
	 * @return Pizza
	 */
	public function createPizza( $type ) {
		$pizza = null;
		switch ( $type ) {
			case 'cheese':
				$pizza = new CheesePizza();
				break;
			case 'clam':
				$pizza = new ClamPizza();
				break;
			case 'veggie':
				$pizza = new VeggiePizza();
				break;
		}

		return $pizza;
	}
}

// 工場のクライアント
class PizzaStore {
	/** @type  SimplePizzaFactory $factory */
	private $factory;

	function __construct( SimplePizzaFactory $factory ) {
		$this->factory = $factory;
	}

	public function order_pizza( $type ) {
		$pizza = $this->factory->createPizza( $type );
		$pizza->prepare();
		$pizza->bake();
		$pizza->cut();
		$pizza->box();

		return $pizza;
	}
}


// ピザストアからピザを注文してみる
$pizza_store  = new PizzaStore( new SimplePizzaFactory() );

$cheese_pizza = $pizza_store->order_pizza( 'cheese' );
$cheese_pizza->describe();

$veggie_pizza = $pizza_store->order_pizza( 'veggie' );
$veggie_pizza->describe();

出力

Preparing Cheese Pizza
BakingCheese Pizza
Cutting Cheese Pizza
Boxing Cheese Pizza
—Cheese Pizza—
Dough: Regular Crust
Sauce: Marinara Pizza Sauce
Topping:
–Fresh Mozzarella
–Parmesan

Preparing Veggie Pizza
Baking Veggie Pizza
Cutting Veggie Pizza
Boxing Veggie Pizza
—Veggie Pizza—
Dough: Crust
Sauce: Marinara Sauce
Topping:
–Shredded mozzarella
–Sliced mushrooms
–Sliced red pepper

ピザオブジェクトを生成する手順を、Simple Factoryを使ってクライアント(PizzaStore)から分離することで、クライアントはピザの種類が何かを心配する必要がなくなり、自分のやるべきことに集中することができるようになりました。

Factory パターン

事業展開して、チェーン店ができた場合について考えてみましょう。
地域によっては生地が違ったり、トッピングが違ったりしますよね。すき焼きはすき焼きでも関西風と関東風があるように、事業展開によってピザの種類は一気に増えるでしょう。
そうなると、「ここのストアならこの種類のピザだ」という風に条件分岐がたくさん増え、読みづらいコードになってしまいます。
そこで、複数のストアクラスを作ってストアごとに違う工場を使うことで、同じ名前のピザでもトッピングが違うようにすることができます。

実装コード

// 工場(PizzaFactory)が作っている商品(Pizza)
abstract class Pizza {
	protected $name, $dough, $sauce;
	protected $toppings = array();

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

	public function prepare() {
		echo 'Preparing ' . $this->name . '<br>';
	}

	public function bake() {
		echo 'Baking ' . $this->name . '<br>';
	}

	public function cut() {
		echo 'Cutting ' . $this->name . '<br>';
	}

	public function box() {
		echo 'Boxing ' . $this->name . '<br>';
	}

	public function describe() {
		echo '---' . $this->name . '---<br>';
		echo 'Dough: ' . $this->dough . '<br>';
		echo 'Sauce: ' . $this->sauce . '<br>';
		echo 'Topping: <br>';
		foreach ( $this->toppings as $topping ) {
			echo ' --' . $topping . '<br>';
		}
		echo '<br><br>';
	}
}

// ピザ工場
interface PizzaFactory {
	/**
	 * @param string $type
	 *
	 * @return Pizza
	 */
	public function create_pizza( $type );
}

// ピザストア
abstract class PizzaStore {
	/** @type  PizzaFactory $factory */
	protected $factory;

	/**
	 * 全チェーン店に共通する製法
	 *
	 * @param string $type
	 *
	 * @return Pizza
	 */
	public function order_pizza( $type ) {
		$pizza = $this->factory->create_pizza( $type );
		$pizza->prepare();
		$pizza->bake();
		$pizza->cut();
		$pizza->box();

		return $pizza;
	}
}


// ------------------- ニューヨーク風ピザ -------------------
class NY_ClamPizza extends Pizza {

	function __construct() {
		$this->name       = 'NY style Clam Pizza';
		$this->dough      = 'Crust Dough';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Grated parmesan';
		$this->toppings[] = 'Diced onion';
	}

}

class NY_VeggiePizza extends Pizza {

	function __construct() {
		$this->name       = 'NY style Veggie Pizza';
		$this->dough      = 'Crust';
		$this->sauce      = 'Marinara Sauce';
		$this->toppings[] = 'Shredded mozzarella';
		$this->toppings[] = 'Sliced mushrooms';
		$this->toppings[] = 'Sliced red pepper';
	}

}

class NY_CheesePizza extends Pizza {

	function __construct() {
		$this->name       = 'NY style Cheese Pizza';
		$this->dough      = 'Regular Crust';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Fresh Mozzarella';
		$this->toppings[] = 'Parmesan';
	}

}


// ------------------- シカゴ風ピザ -------------------
class Chicago_ClamPizza extends Pizza {

	function __construct() {
		$this->name       = 'Chicago style Clam Pizza';
		$this->dough      = 'Regular Crust';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Grated parmesan';
		$this->toppings[] = 'Diced onion';
	}
}

class Chicago_VeggiePizza extends Pizza {

	function __construct() {
		$this->name       = 'Chicago style Veggie Pizza';
		$this->dough      = 'Crust';
		$this->sauce      = 'Marinara Sauce';
		$this->toppings[] = 'Shredded mozzarella';
		$this->toppings[] = 'Sliced red pepper';
	}
}

class Chicago_CheesePizza extends Pizza {

	function __construct() {
		$this->name       = 'Chicago style Cheese Pizza';
		$this->dough      = 'Thin Crust Dough';
		$this->sauce      = 'Marinara Pizza Sauce';
		$this->toppings[] = 'Fresh Mozzarella';
		$this->toppings[] = 'Parmesan';
	}
}

// ニューヨークのピザ工場
class NY_PizzaFactory implements PizzaFactory {
	public function create_pizza( $type ) {
		$pizza = null;
		switch ( $type ) {
			case 'cheese':
				$pizza = new NY_CheesePizza();
				break;
			case 'clam':
				$pizza = new NY_ClamPizza();
				break;
			case 'veggie':
				$pizza = new NY_VeggiePizza();
				break;
		}

		return $pizza;
	}
}

// シカゴのピザ工場
class Chicago_PizzaFactory implements PizzaFactory {
	public function create_pizza( $type ) {
		$pizza = null;
		switch ( $type ) {
			case 'cheese':
				$pizza = new Chicago_CheesePizza();
				break;
			case 'clam':
				$pizza = new Chicago_ClamPizza();
				break;
			case 'veggie':
				$pizza = new Chicago_VeggiePizza();
				break;
		}

		return $pizza;
	}
}


// ニューヨークのストア
class NY_PizzaStore extends PizzaStore {

	function __construct() {
		$this->factory = new NY_PizzaFactory();
	}
}

// シカゴのストア
class Chicago_PizzaStore extends PizzaStore {

	function __construct() {
		$this->factory = new Chicago_PizzaFactory();
	}
}


// ニューヨークとシカゴのピザストアからピザを注文してみる
$ny_pizza_store = new NY_PizzaStore();
$cheese_pizza   = $ny_pizza_store->order_pizza( 'cheese' );
$cheese_pizza->describe();

$veggie_pizza = $ny_pizza_store->order_pizza( 'veggie' );
$veggie_pizza->describe();

$chicago_pizza_store = new Chicago_PizzaStore();
$cheese_pizza        = $chicago_pizza_store->order_pizza( 'cheese' );
$cheese_pizza->describe();

$veggie_pizza = $chicago_pizza_store->order_pizza( 'veggie' );
$veggie_pizza->describe();

出力

Preparing NY style Cheese Pizza
Baking NY style Cheese Pizza
Cutting NY style Cheese Pizza
Boxing NY style Cheese Pizza
—NY style Cheese Pizza—
Dough: Regular Crust
Sauce: Marinara Pizza Sauce
Topping:
–Fresh Mozzarella
–Parmesan

Preparing NY style Veggie Pizza
Baking NY style Veggie Pizza
Cutting NY style Veggie Pizza
Boxing NY style Veggie Pizza
—NY style Veggie Pizza—
Dough: Crust
Sauce: Marinara Sauce
Topping:
–Shredded mozzarella
–Sliced mushrooms
–Sliced red pepper

Preparing Chicago style Cheese Pizza
Baking Chicago style Cheese Pizza
Cutting Chicago style Cheese Pizza
Boxing Chicago style Cheese Pizza
—Chicago style Cheese Pizza—
Dough: Thin Crust Dough
Sauce: Marinara Pizza Sauce
Topping:
–Fresh Mozzarella
–Parmesan

Preparing Chicago style Veggie Pizza
Baking Chicago style Veggie Pizza
Cutting Chicago style Veggie Pizza
Boxing Chicago style Veggie Pizza
—Chicago style Veggie Pizza—
Dough: Crust
Sauce: Marinara Sauce
Topping:
–Shredded mozzarella
–Sliced red pepper

UML図

abstract_factory_UML

まとめ

いかがでしたでしょうか?
冒頭でも触れましたが、このパターンはインスタンス化の手順が複雑化してきた場合に真価を発揮します。サンプルコードでは結構シンプルだったので、そのメリットをあまり感じてもらえなかったかもしれませんが、意図は伝わったのではないでしょうか。

実は上記の実装例はいわゆる「Abstract-Factory」パターンに分類されるもので、「Factory」とはほんの少しだけ違うのですが、個人的にはどうも根本的な考え方は一緒だという認識があり、わざわざ分ける必要はないと思ったため、あえてパターンのクラス図を示しませんでした。

この類のパターンの考え方はすごく単純です。「あるオブジェクトがほかのオブジェクトを生成」する、そしてその結果、誤ったインスタンス化を防ぎ、さらにコードの柔軟性をもたらす。たったこれだけの話なので、これといったオフィシャル的なやり方はないと思っていいと思います。

それでは、また。

 

【前回までのPHPで学ぶデザインパターン】

【PHPで学ぶデザインパターン入門】第1回 Strategyパターン

【PHPで学ぶデザインパターン入門】第2回 Decoratorパターン

【PHPで学ぶデザインパターン入門】第3回 Stateパターン

【PHPで学ぶデザインパターン入門】第4回 Iteratorパターン