FactoryMethodパターンを使ってみる
FactoryMethodパターンとは
Factory Methodパターンは、オブジェクト生成の責任をサブクラスに委譲するデザインパターンです。
オブジェクトの生成をカプセル化することで依存度を下げることができます。
抽象クラス(親クラス)に抽象メソッドとして、オブジェクトの生成を定義しますが、具体的にどのクラスをインスタンス化するかは子クラスが決定します。
そのため、親クラスは子クラスが作成したオブジェクトを利用しますが、具体的なオブジェクトについては知らなくて済み、具体的なオブジェクトに依存しなくなります。
FactoryMethodパターンのクラス図
環境
- Laravel 9.38.0
- PHP 8.1.12
Laravel Sailで環境構築しています。
https://readouble.com/laravel/9.x/ja/sail.html
参考
今回の実装について
- 地域ごとにフランチャイズ展開されるピザショップを想定
- 注文された後の工程は基本的に同一
- 地域ごとにピザの内容は異なる
クラス図
クラス名 | 説明 |
---|---|
PizzaStore | 抽象クラス。ピザの注文処理とピザの生成メソッドを持つ。 |
NYPizzaStore | ニューヨークスタイルのピザを生成する具象クラス。 具体的なピザオブジェクトの生成を行う。 |
ChicagoPizzaStore | シカゴスタイルのピザを生成する具象クラス。 具体的なピザオブジェクトの生成を行う。 |
Pizza | 抽象クラス。ピザの基本情報と調理、焼く、カット、箱詰めするメソッドを持つ。 |
NYCheesePizza | ニューヨークスタイルのチーズピザを表現する具象クラス。 |
NYStyleClamPizza | ニューヨークスタイルのクラムピザを表現する具象クラス。 |
ChicagoCheesePizza | シカゴスタイルのチーズピザを表現する具象クラス。 |
ChicagoClamPizza | シカゴスタイルのクラムピザを表現する具象クラス。 |
①PizzaStore
抽象クラス(親クラス)として、PizzaStoreクラスを実装します。
ピザの注文処理とピザの生成を抽象メソッドとして持ちます。
具体的なピザオブジェクトについて知る必要がなく、抽象ピザクラスにのみ依存します。
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クラスを実装します。
基本情報と調理、焼く、カット、箱詰めするメソッドを持ちます。
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を継承し、具象クラスのインスタンス化を担います。
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;
}
}
④ニューヨークスタイルの具象クラス
ニューヨークスタイルの具象ピザクラスを実装します。
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 = ['ゴルゴンゾーラチーズ', 'オリーブオイル'];
}
}
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を継承し、具象クラスのインスタンス化を担います。
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メソッドをオーバーライドします。
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";
}
}
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