策略模式定義演算法家族,分別封裝並使其可互換,讓演算法變化獨立於使用演算法的客戶端。本文以鴨子模擬器為例,展示飛行行為 (FlyBehavior) 與叫聲行為 (QuackBehavior) 策略介面及其多種實作,透過組合方式注入鴨子類別,實現執行時動態切換行為,深度體現「多用組合、少用繼承」設計原則。
<?php
// 飛行行為介面
interface FlyBehavior {
public function fly();
}
// 叫聲行為介面
interface QuackBehavior {
public function quack();
}
?>
{// 飛行策略實作
class FlyWithWings implements FlyBehavior {
public function fly() { echo "I'm flying!\n"; }
}
class FlyNoWay implements FlyBehavior {
public function fly() { echo "I can't fly.\n"; }
}
class FlyRocketPowered implements FlyBehavior {
public function fly() { echo "I'm flying with a rocket!\n"; }
}
// 叫聲策略實作
class Quack implements QuackBehavior {
public function quack() { echo "Quack!\n"; }
}
class MuteQuack implements QuackBehavior {
public function quack() { echo "Silence\n"; }
}
abstract class Duck {
protected $flyBehavior;
protected $quackBehavior;
public function __construct() {}
// 委派給策略物件執行
public function performFly() {
$this->flyBehavior->fly();
}
public function performQuack() {
$this->quackBehavior->quack();
}
// 執行時動態切換策略
public function setFlyBehavior(FlyBehavior $fb) {
$this->flyBehavior = $fb;
}
public function setQuackBehavior(QuackBehavior $qb) {
$this->quackBehavior = $qb;
}
public function swim() {
echo "All ducks float, even decoys!\n";
}
// 子類必須實作顯示外觀
abstract public function display();
}
class MallardDuck extends Duck {
public function __construct() {
// 預設策略:不會飛、普通叫聲
$this->quackBehavior = new Quack();
$this->flyBehavior = new FlyNoWay();
}
public function display() {
echo "I'm a real Mallard duck\n";
}
}
<?php
require_once 'Duck.php';
require_once 'MallardDuck.php';
require_once 'FlyBehavior.php';
require_once 'FlyWithWings.php';
require_once 'FlyNoWay.php';
require_once 'FlyRocketPowered.php';
$duck = new MallardDuck();
$duck->display(); // I'm a real Mallard duck
$duck->performFly(); // I can't fly. (預設 FlyNoWay)
// 執行時動態改變行為:裝上火箭推進器!
$duck->setFlyBehavior(new FlyRocketPowered());
$duck->performFly(); // I'm flying with a rocket!
?>
I'm a real Mallard duck
I can't fly.
I'm flying with a rocket!
| 優勢 | 說明 | 本例體現 |
|---|---|---|
| 演算法封裝 | 每個策略類別封裝單一演算法變體 | FlyWithWings/FlyNoWay/FlyRocketPowered 分別封裝不同飛行邏輯 |
| 可互換 | 相同介面策略可自由替換 | setFlyBehavior() 任意切換飛行策略 |
| 獨立變化 | 新增策略不修改客戶端 (Duck) | 新增 FlyJetPowered 只需實作 FlyBehavior,Duck 完全不用動 |
| 執行時切換 | 物件生命週期中可動態改變行為 | $duck->setFlyBehavior(new FlyRocketPowered()) 即時生效 |
| 避免繼承爆炸 | 不用為每種組合建立子類 | 不需 MallardDuckWithRocket、MallardDuckNoFly 等子類 |
MallardDuck, MallardDuckWithRocket, RedheadDuck, RedheadDuckWithRocket... 類別數量 = 鴨子種類 × 飛行策略 × 叫聲策略,指數級增長。策略模式透過 介面定義契約、具體類別封裝演算法、組合注入客戶端、執行時動態切換 四步驟,實現演算法與使用者解耦。核心代碼 setFlyBehavior(new FlyRocketPowered()) 生動展示「組合優於繼承」:鴨子不繼承飛行能力,而是擁有飛行策略物件。這是物件導向設計從「是一種」關係轉向「有一種」關係的經典範例。