Decorator Pattern,PHP 裝飾者模式

Decorator Pattern(裝飾者模式)是一種結構型設計模式,它用於在不改變原有物件的情況下,為物件添加新的功能。這種模式通常會使用一個裝飾者類別,用來包裝原有的類別實例。
本文使用一個範例,展示未使用和使用 Decorator Pattern 的差異,藉以了解 Decorator Pattern 的優點與實際用法。
Coffee 範例(未使用 Decorator Pattern)
這段 PHP 程式碼展示了如何使用多個不同的類別來代表不同種類的咖啡,而非使用 Decorator Pattern。我將分步驟解釋每個部分。
STEP 1:SimpleCoffee 類別
這個類別定義了最基本的咖啡,包括其成本和描述。
class SimpleCoffee {
public function getCost() {
return 10;
}
public function getDescription() {
return 'Simple coffee';
}
}STEP 2:MilkCoffee 類別
這個類別代表了加了牛奶的咖啡,也有其自己的成本和描述。
class MilkCoffee {
public function getCost() {
return 12;
}
public function getDescription() {
return 'Simple coffee, milk';
}
}STEP 3:ChocolateCoffee 類別
這個類別代表了加了巧克力的咖啡。注意,這裡不是在 MilkCoffee 基礎上添加巧克力,而是直接創建了一個全新的類別。
class ChocolateCoffee {
public function getCost() {
return 17;
}
public function getDescription() {
return 'Simple coffee, chocolate';
}
}STEP 4:ChocolateMilkCoffee 類別
這個類別代表了加了牛奶和兩倍巧克力的咖啡。它也有自己獨立的成本和描述。
class ChocolateMilkCoffee {
public function getCost() {
return 30;
}
public function getDescription() {
return 'Simple coffee, milk, chocolate';
}
}STEP 5:使用各種咖啡類別
最後,程式碼示範了如何使用這些咖啡類別來獲得成本和描述。
// 使用
$coffee = new SimpleCoffee();
echo $coffee->getCost(); // 輸出: 10
echo $coffee->getDescription(); // 輸出: Simple coffee
$milkCoffee = new MilkCoffee();
echo $milkCoffee->getCost(); // 輸出: 12
echo $milkCoffee->getDescription(); // 輸出: Simple coffee, milk
$chocolateCoffee = new ChocolateCoffee();
echo $chocolateCoffee->getCost(); // 輸出: 17
echo $chocolateCoffee->getDescription(); // 輸出: Simple coffee, chocolate
$chocolateMilkCoffee = new ChocolateMilkCoffee();
echo $chocolateMilkCoffee->getCost(); // 輸出: 30
echo $chocolateMilkCoffee->getDescription(); // 輸出: Simple coffee, milk, chocolate這樣的設計有一些明顯的缺點:
程式碼重複:每一種不同的咖啡(如有牛奶、有糖等)都需要一個全新的類別。這會導致大量重複的程式碼。
不易擴展:如果你想為咖啡添加新的修飾(例如糖、冰塊等),你將需要創建更多新的類別。這樣會讓系統變得非常冗長和難以維護。
違反開放/封閉原則:每次添加新的修飾,你都需要修改現有的程式碼,這違反了軟體應該對擴展開放,對修改封閉的開放/封閉原則。
維護困難:如同上面所述,一旦基本咖啡的價格或其他特性需要更改,你可能需要在多個類別中進行修改。
改善作法 (使用 Decorator Pattern)
STEP 1:定義基本介面
首先,我們需要一個共同的介面,以便所有的咖啡和咖啡的裝飾者都可以實現它。這個介面包括 getCost() 和 getDescription() 這兩個方法。
<?php
interface Coffee {
public function getCost();
public function getDescription();
}
STEP 2:建立基本咖啡類別
接著,建立一個簡單咖啡(SimpleCoffee)類別,這個類別會實作上面定義的 Coffee 介面。
class SimpleCoffee implements Coffee {
public function getCost() {
return 10;
}
public function getDescription() {
return 'Simple coffee';
}
}這個簡單咖啡類代表了最基本的咖啡,它有固定的價格和描述。
STEP 3:建立牛奶裝飾者
現在,開始進入 Decorator Pattern 的核心部分。首先,建立一個加牛奶的咖啡(MilkCoffee)裝飾者。
class MilkCoffee implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost() + 2;
}
public function getDescription() {
return $this->coffee->getDescription() . ', milk';
}
}STEP 4:實際使用
現在,我們可以創建一個咖啡物件並用裝飾者來裝飾它。
// 創建一個簡單咖啡物件
$coffee = new SimpleCoffee();
// 用加牛奶的咖啡裝飾它
$coffee = new MilkCoffee($coffee);
// 輸出結果
echo $coffee->getCost(); // 輸出: 12
echo $coffee->getDescription(); // 輸出: Simple coffee, milkSTEP 5:擴展 ChocolateCoffee 裝飾者
現在可以很容易地擴展 Decorator 模式,加入一個新的 ChocolateCoffee 裝飾者。
class ChocolateCoffee implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost() + 5;
}
public function getDescription() {
return $this->coffee->getDescription() . ', chocolate';
}
}STEP 6:實際使用 ChocolateCoffee
接著,再次使用裝飾者來裝飾咖啡物件。
// 創建一個簡單咖啡物件
$coffee = new SimpleCoffee();
// 用牛奶咖啡裝飾它
$coffee = new MilkCoffee($coffee);
// 再用巧克力咖啡裝飾它
$coffee = new ChocolateCoffee($coffee);
// 輸出結果
echo $coffee->getCost(); // 輸出: 17
echo $coffee->getDescription(); // 輸出: Simple coffee, milk, chocolate如您所見,添加 ChocolateCoffee 裝飾者變得非常容易和靈活。我們只需要創建一個新的裝飾者類別,並在其中實作我們想要添加或修改的功能。這樣做不會影響到其他部分的程式碼,使得整個系統更容易擴展和維護。
這個模式允許我們以很高的靈活性來組合不同的功能,而不需要創建大量的新類別來表示每一種可能的組合。
發揮 Decorator 的極限
使用目前已有的 Decorator(MilkCoffee 和 ChocolateCoffee),就可以創建各種不同的咖啡組合。以下是一些範例:
單純的咖啡
$coffee = new SimpleCoffee();
echo $coffee->getCost(); // 輸出: 10
echo $coffee->getDescription(); // 輸出: Simple coffee加牛奶的咖啡
$coffee = new SimpleCoffee();
$coffee = new MilkCoffee($coffee);
echo $coffee->getCost(); // 輸出: 12
echo $coffee->getDescription(); // 輸出: Simple coffee, milk加巧克力的咖啡
$coffee = new SimpleCoffee();
$coffee = new ChocolateCoffee($coffee);
echo $coffee->getCost(); // 輸出: 15
echo $coffee->getDescription(); // 輸出: Simple coffee, chocolate加牛奶和巧克力的咖啡
$coffee = new SimpleCoffee();
$coffee = new MilkCoffee($coffee);
$coffee = new ChocolateCoffee($coffee);
echo $coffee->getCost(); // 輸出: 17
echo $coffee->getDescription(); // 輸出: Simple coffee, milk, chocolate雙倍牛奶的咖啡
$coffee = new SimpleCoffee();
$coffee = new MilkCoffee($coffee);
$coffee = new MilkCoffee($coffee);
echo $coffee->getCost(); // 輸出: 14
echo $coffee->getDescription(); // 輸出: Simple coffee, milk, milk雙倍巧克力的咖啡
$coffee = new SimpleCoffee();
$coffee = new ChocolateCoffee($coffee);
$coffee = new ChocolateCoffee($coffee);
echo $coffee->getCost(); // 輸出: 20
echo $coffee->getDescription(); // 輸出: Simple coffee, chocolate, chocolate雙倍牛奶和雙倍巧克力的咖啡
$coffee = new SimpleCoffee();
$coffee = new MilkCoffee($coffee);
$coffee = new MilkCoffee($coffee);
$coffee = new ChocolateCoffee($coffee);
$coffee = new ChocolateCoffee($coffee);
echo $coffee->getCost(); // 輸出: 22
echo $coffee->getDescription(); // 輸出: Simple coffee, milk, milk, chocolate, chocolate這樣,我們就創建了 7 種不同的咖啡,而只需要使用 1 個基礎類別和 2 個裝飾者。這展示了使用 Decorator Pattern 的高靈活性和易於擴展的特點。
- ← Previous
21 個常用的 git commands 教學,讓你的開發流程更有效率 - Next →
Adapter Pattern,PHP 適配器模式



