簡介
Strategy Pattern 允許你在運行時改變物件的行為。這一模式是行為設計模式的一種,主要用於將一個類的具體實作從其他相關的類中抽離出來,以減少模組間的耦合性。這表示一個系統可以從如何實現某種算法或行為中解耦出來,這些算法或行為可以獨立於使用它們的系統進行更改。
Strategy Pattern 主要涉及三個部分:策略介面(Strategy Interface)、具體策略(Concrete Strategies)和上下文(Context)。策略介面定義了一系列的行為,具體策略則是這些行為的實現,而上下文則用於切換不同的策略。
什麼是 Strategy Pattern
Strategy Pattern(策略模式)就像是一個遊戲中不同的攻擊方式或是做咖啡的不同配方。它允許您將一個特定的任務或算法(比如說,製作咖啡)封裝起來,並且讓您可以隨時切換到另一個算法或任務(比如說,由製作濃縮咖啡切換到拿鐵或摩卡)。
舉例來說,想像您有一台咖啡機,這台機器可以製作多種類型的咖啡,例如濃縮咖啡、拿鐵或摩卡。如果不用策略模式,您可能需要為機器寫很多的 if-else
判斷語句來決定要製作哪一種咖啡。但這樣一來,如果將來要添加新的咖啡種類,您可能需要修改很多地方的程式碼。
使用策略模式,您只需為每種咖啡類型定義一個「策略」。然後,您可以讓咖啡機持有一個策略,並隨時更換它。這樣,要添加新的咖啡類型,您只需要新增一個新的策略,而不必改動咖啡機的程式碼。
什麼狀況適合使用 Strategy Pattern
- 當有多種算法可以解決同一個問題時,例如排序算法、支付方式等。
- 當需要在運行時動態地更改行為或算法時。
- 當一個類有多個行為,而你希望避免出現大量條件語句時。
- 當你希望維護和增加新策略時不影響現有代碼。
範例教學
假設我們有一個咖啡機,它可以根據不同的策略來釀造咖啡。
原始範例 (未使用 Strategy)
class CoffeeMachine {
public function makeCoffee($type) {
if ($type === 'Espresso') {
echo "Making an Espresso\n";
} elseif ($type === 'Latte') {
echo "Making a Latte\n";
}
// 更多類型
}
}
每次新增或修改咖啡類型都需要修改 CoffeeMachine
類,這樣違反了開放封閉原則。
改善範例 (使用 Strategy)
// 定義一個任何咖啡沖泡策略都必須實現的介面
interface CoffeeStrategy {
public function brew(); // 每個策略都必須定義的方法
}
// 實現 CoffeeStrategy 接口以沖泡濃咖啡(Espresso)
class EspressoStrategy implements CoffeeStrategy {
public function brew() {
echo "Making an Espresso\n"; // 指示正在製作濃咖啡
}
}
// 實現 CoffeeStrategy 介面以沖泡拿鐵(Latte)
class LatteStrategy implements CoffeeStrategy {
public function brew() {
echo "Making a Latte\n"; // 指示正在製作拿鐵
}
}
// CoffeeMachine 類別,將使用不同的沖泡策略
class CoffeeMachine {
private $strategy; // 當前的沖泡策略
// 設置方法,用於設置新的沖泡策略
public function setStrategy(CoffeeStrategy $strategy) {
$this->strategy = $strategy;
}
// 使用當前沖泡策略來製作咖啡的方法
public function makeCoffee() {
$this->strategy->brew(); // 調用當前策略的 brew 方法
}
}
// 創建一個新的 CoffeeMachine 實例
$machine = new CoffeeMachine();
// 將沖泡策略設置為濃咖啡(Espresso),並製作咖啡
$machine->setStrategy(new EspressoStrategy());
$machine->makeCoffee(); // 輸出:Making an Espresso
// 將沖泡策略更改為拿鐵(Latte),並製作咖啡
$machine->setStrategy(new LatteStrategy());
$machine->makeCoffee(); // 輸出:Making a Latte
通過使用策略模式,可以輕鬆地添加或修改不同類型的咖啡釀造方式,而不會影響 CoffeeMachine
類別。
更多延伸範例
// 新增兩個咖啡策略:Cappuccino 和 Mocha
class CappuccinoStrategy implements CoffeeStrategy {
public function brew() {
echo "Making a Cappuccino\n";
}
}
class MochaStrategy implements CoffeeStrategy {
public function brew() {
echo "Making a Mocha\n";
}
}
// 使用新策略
$machine = new CoffeeMachine();
$machine->setStrategy(new CappuccinoStrategy());
$machine->makeCoffee(); // Output: Making a Cappuccino
$machine->setStrategy(new MochaStrategy());
$machine->makeCoffee(); // Output: Making a Mocha
你也可以進一步擴展,例如添加一個用於添加糖或奶的策略。
// 定義一個添加物策略接口,任何添加物都必須實現這個接口
interface AdditiveStrategy {
public function add(); // 每個添加物策略都必須定義的方法
}
// 實現添加糖的策略
class SugarStrategy implements AdditiveStrategy {
public function add() {
echo "Adding sugar\n"; // 打印消息,指示正在添加糖
}
}
// 實現添加牛奶的策略
class MilkStrategy implements AdditiveStrategy {
public function add() {
echo "Adding milk\n"; // 打印消息,指示正在添加牛奶
}
}
// 進階的咖啡機,繼承自 CoffeeMachine 並新增添加物功能
class AdvancedCoffeeMachine extends CoffeeMachine {
private $additiveStrategy; // 用於咖啡添加物的策略
// 設置添加物策略
public function setAdditiveStrategy(AdditiveStrategy $additiveStrategy) {
$this->additiveStrategy = $additiveStrategy;
}
// 使用當前的咖啡和添加物策略來製作咖啡
public function makeCoffeeWithAdditives() {
$this->strategy->brew(); // 調用咖啡沖泡策略
$this->additiveStrategy->add(); // 調用添加物策略
}
}
// 使用進階咖啡機
$advancedMachine = new AdvancedCoffeeMachine();
// 設置沖泡濃咖啡(Espresso)的策略
$advancedMachine->setStrategy(new EspressoStrategy());
// 設置添加糖的策略
$advancedMachine->setAdditiveStrategy(new SugarStrategy());
// 製作咖啡並添加糖
$advancedMachine->makeCoffeeWithAdditives();
// Output: Making an Espresso
// Output: Adding sugar
這個進階咖啡機示例展示了如何組合不同類型的策略(釀造和添加),從而提供更多的客製化選項。這是 Strategy Pattern 的一大優點,即易於擴展和組合不同的行為或算法。
其他類似的 Design Pattern
- Command Pattern:也能用來封裝行為,但是它用於封裝完整的請求。
- State Pattern:允許一個物件在其內部狀態改變時改變其行為,這看起來就像物件改變了其類。
Strategy Pattern 專注於行為或算法的更改,而不是物件或其狀態的更改。
參考資料: