裝飾器模式屬於結構型設計模式,允許在不改變原有類別、不使用繼承的情況下,動態地為物件添加新行為。就像「大口袋套小口袋」,層層包裹核心物件。
<?php
// 1. 抽象元件介面
interface Food {
public function getDescription(): string;
public function getCost(): float;
}
// 2. 具體元件:基礎飯
class Rice implements Food {
public function getDescription(): string { return "白飯"; }
public function getCost(): float { return 20.0; }
}
// 3. 裝飾者基底:抽象菜色
abstract class SideDish implements Food {
protected Food $food; // 持有被裝飾物件
public function __construct(Food $food) {
$this->food = $food;
}
// 預設轉發給被裝飾物件
public function getDescription(): string {
return $this->food->getDescription();
}
public function getCost(): float {
return $this->food->getCost();
}
}
// 4. 具體裝飾者:各種菜色
class BraisedPork extends SideDish {
public function getDescription(): string {
return $this->food->getDescription() . " + 滷肉";
}
public function getCost(): float {
return $this->food->getCost() + 30.0;
}
}
class TeaEgg extends SideDish {
public function getDescription(): string {
return $this->food->getDescription() . " + 茶葉蛋";
}
public function getCost(): float {
return $this->food->getCost() + 10.0;
}
}
class Pickles extends SideDish {
public function getDescription(): string {
return $this->food->getDescription() . " + 醃菜";
}
public function getCost(): float {
return $this->food->getCost() + 5.0;
}
}
// 5. 客戶端使用:動態組合
$lunch = new Rice(); // 基礎:白飯 $20
$lunch = new BraisedPork($lunch); // 加滷肉 $30
$lunch = new TeaEgg($lunch); // 加茶蛋 $10
$lunch = new Pickles($lunch); // 加醃菜 $5
echo "餐點: " . $lunch->getDescription() . "\n"; // 白飯 + 滷肉 + 茶葉蛋 + 醃菜
echo "總價: $" . $lunch->getCost() . "\n"; // $65
?>
+----------------+ +------------------+ | <>| | Food | | Food |<|-----| (Decorator) | |----------------| |------------------| | +getDescription| | -food: Food | | +getCost() | |------------------| +----------------+ | +__construct() | ^ | +getDescription()| | | +getCost() | | +------------------+ | ^ | | +-------+--------+ +---------+----------+ | Rice | | BraisedPork | |----------------| |------------------- | | +getDescription| | +getDescription() | | +getCost() | | +getCost() | +----------------+ +--------------------+ ^ +---------+----------+ | | | +--------+----------+ | | TeaEgg | | |------------------- | | | +getDescription() | | | +getCost() | | +--------------------+ | +---------+----------+ | | | +--------+----------+ | | Pickles | | |------------------- | | | +getDescription() | | | +getCost() | | +--------------------+
| 維度 | 繼承 | 裝飾器模式 |
|---|---|---|
| 靈活性 | 靜態、編譯時決定 | 動態、執行時組合 |
| 類別爆炸 | 易產生大量子類別 (RiceWithPork, RiceWithEgg, RiceWithPorkAndEgg...) | 避免類別爆炸,組合數 = 排列組合 |
| 單一職責 | 違反 (功能混雜在子類別) | 符合 (每個裝飾者單一功能) |
| 開放封閉 | 修改基底類別才能擴展 | 新增裝飾者不修改既有代碼 |
| 適用場景 | 「是一種」關係、穩定層級 | 「有某功能」關係、頻繁變動組合 |
new BufferedReader(new FileReader(new GZIPInputStream(new FileInputStream("file.gz")))) (Java 風格)$img = new Watermark(new Resize(new Crop(new Image("src.jpg"))))$cache = new RedisCache(new FileCache(new NullCache($service)))$validator = new EmailRule(new RequiredRule(new LengthRule($input)))IteratorAggregate + 裝飾器<?php
class LoggingIterator implements IteratorAggregate {
private $inner;
public function __construct(Traversable $inner) { $this->inner = $inner; }
public function getIterator(): Traversable {
echo "開始迭代\n";
foreach ($this->inner as $k => $v) {
echo "存取: $k => $v\n";
yield $k => $v;
}
echo "結束迭代\n";
}
}
$data = [1,2,3];
$logged = new LoggingIterator(new ArrayIterator($data));
foreach ($logged as $v) { /* ... */ }
?>
new A(new B($obj)) ≠ new B(new A($obj))instanceof 只能檢測最外層,需遞迴檢查內層