Strategy Pattern,PHP 策略模式

depth of field photography of man playing chess

簡介

Strategy Pattern 允許你在運行時改變物件的行為。這一模式是行為設計模式的一種,主要用於將一個類的具體實作從其他相關的類中抽離出來,以減少模組間的耦合性。這表示一個系統可以從如何實現某種算法或行為中解耦出來,這些算法或行為可以獨立於使用它們的系統進行更改。

Strategy Pattern 主要涉及三個部分:策略介面(Strategy Interface)、具體策略(Concrete Strategies)和上下文(Context)。策略介面定義了一系列的行為,具體策略則是這些行為的實現,而上下文則用於切換不同的策略。

Strategy Pattern 類別圖

什麼是 Strategy Pattern

Strategy Pattern(策略模式)就像是一個遊戲中不同的攻擊方式或是做咖啡的不同配方。它允許您將一個特定的任務或算法(比如說,製作咖啡)封裝起來,並且讓您可以隨時切換到另一個算法或任務(比如說,由製作濃縮咖啡切換到拿鐵或摩卡)。

舉例來說,想像您有一台咖啡機,這台機器可以製作多種類型的咖啡,例如濃縮咖啡、拿鐵或摩卡。如果不用策略模式,您可能需要為機器寫很多的 if-else 判斷語句來決定要製作哪一種咖啡。但這樣一來,如果將來要添加新的咖啡種類,您可能需要修改很多地方的程式碼。

使用策略模式,您只需為每種咖啡類型定義一個「策略」。然後,您可以讓咖啡機持有一個策略,並隨時更換它。這樣,要添加新的咖啡類型,您只需要新增一個新的策略,而不必改動咖啡機的程式碼。

什麼狀況適合使用 Strategy Pattern

  1. 當有多種算法可以解決同一個問題時,例如排序算法、支付方式等。
  2. 當需要在運行時動態地更改行為或算法時。
  3. 當一個類有多個行為,而你希望避免出現大量條件語句時。
  4. 當你希望維護和增加新策略時不影響現有代碼。

範例教學

假設我們有一個咖啡機,它可以根據不同的策略來釀造咖啡。

原始範例 (未使用 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 專注於行為或算法的更改,而不是物件或其狀態的更改。

參考資料:

  1. PHP Design Patterns
  2. Wikipedia: Strategy pattern

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *