Decoratorパターン
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 77/91。
Decorator Patternは、デコレーターと呼ばれる特別なラッパーオブジェクトでオブジェクトをラップすることにより、新しい振る舞いを動的に追加できる構造設計パターンです。コンパイル時に振る舞いを追加する継承とは異なり、デコレーターは実行時に振る舞いを追加します。
コーヒーショップを想像してみてください。まず基本のコーヒーから始め、次に Milk、砂糖、ホイップクリームなどの追加を加えます。
それぞれの追加要素は元のcoffeeをラップし、そのdescriptionとコストにaddsします。重要な洞察は、base coffeeとデコレーターの両方が同じinterfaceを共有していることです。
<?php
interface Coffee {
public function getDescription(): string;
public function getCost(): float;
}
class SimpleCoffee implements Coffee {
public function getDescription(): string {
return "Simple Coffee";
}
public function getCost(): float {
return 2.00;
}
}
Decoratorは同じinterfaceを実装し、別のCoffeeオブジェクトをラップします。
<?php
class MilkDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Milk";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.50;
}
}
class SugarDecorator implements Coffee {
public function __construct(private Coffee $coffee) {}
public function getDescription(): string {
return $this->coffee->getDescription() . ", Sugar";
}
public function getCost(): float {
return $this->coffee->getCost() + 0.25;
}
}
$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);
echo $coffee->getDescription() . " = $" . $coffee->getCost();
出力:
Simple Coffee, Milk, Sugar = $2.75each decorator は前の object をラップし、チェーンを形成します。このパターンを使うと、考えられるすべての組み合わせごとに class を作成することなく、動作を任意の順序と数量で組み合わせられます。Milk を2倍にしたいですか? そのまま2回ラップしてください。
チャレンジ
簡単デコレータパターンを使って、ピザの注文システムを構築しましょう。まず基本的なピザから始め、トッピングを動的に追加します。各トッピングはピザをラップし、その説明と価格に追加します。これはレッスンの coffee の例と同じです。
コードを4つのファイルに分けて整理します。
Pizza.php:すべてのピザとトッピングの契約を定義するPizzainterface を Define します。次の2つの methods を持たせます:getDescription(): stringとgetPrice(): float。BasePizza.php:Pizza interface を読み込み、それを implements するMargheritaPizzaclass を Create します。これは、description が"Margherita"、price が8.00の base pizza です。Toppings.php:Pizza interface を読み込み、それを implements する3つの decorator class を Create します。各 decorator はPizzaobject をラップし(constructor で受け取ります)、description と price の両方を拡張します。CheeseTopping:description に", Extra Cheese"を、price に1.50を addsPepperoniTopping:description に", Pepperoni"を、price に2.00を addsMushroomTopping:description に", Mushrooms"を、price に1.25を adds
main.php:BasePizza ファイルと Toppings ファイルを読み込みます。追加するトッピングの comma 区切りの list を1つ input として受け取ります(例:"cheese,pepperoni"または"mushroom,cheese,cheese")。MargheritaPizzaから始めます。input 内の each トッピングについて、appropriate decorator でピザをラップします。"cheese"の場合はCheeseToppingでラップします"pepperoni"の場合はPepperoniToppingでラップします"mushroom"の場合はMushroomToppingでラップします
すべてのトッピングを適用した後、final pizza の description と price を次の format で出力します。
[description] = $[price]price は小数点以下2桁に Format してください。
同じトッピングを複数回適用できることに注目してください。ダブルチーズを注文する場合は、単に CheeseTopping で2回ラップします。この柔軟性こそが、デコレータパターンの力です。
自分で試してみよう
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// 入力を読み取る - トッピングのカンマ区切りリスト
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// ベースのMargheritaPizzaから始める
$pizza = new MargheritaPizza();
// TODO: 各トッピングをループし、適切なデコレータでピザをラップする
// - "cheese" -> CheeseToppingでラップする
// - "pepperoni" -> PepperoniToppingでラップする
// - "mushroom" -> MushroomToppingでラップする
// TODO: 結果を次の形式で出力する: [description] = $[price]
// number_format()を使って価格を小数点以下2桁にフォーマットする
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1OOPの基礎
外部ファイルOOP入門クラスとオブジェクトの違い$thisキーワードメソッドプロパティコンストラクター(__construct)デストラクター(__destruct)復習 - 簡単な電卓13デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンRepositoryパターン自分で練習してみよう: PHPオンラインコンパイラ