Strategy Pattern(策略模式)

是什麼?

Strategy Pattern 把一組可以互換的演算法封裝起來,讓使用它的程式碼可以在執行時切換不同的演算法,而不需要修改自己的程式碼。

ℹ️GoF 分類

Strategy 屬於行為型模式(Behavioral Pattern),重點在於物件之間如何分配職責和溝通。

什麼時候用?

用以下 if/then 條件判斷:

常見應用場景

| 場景 | 策略介面 | 具體策略範例 | |------|---------|-------------| | 電商付款 | IPaymentStrategy | 信用卡、Line Pay、銀行轉帳、貨到付款 | | 資料排序 | ISortStrategy | 快速排序、合併排序、堆積排序 | | 訊息通知 | INotificationStrategy | Email、SMS、Push Notification | | 文件壓縮 | ICompressionStrategy | ZIP、GZIP、LZ4 | | 定價折扣 | IDiscountStrategy | 會員折扣、滿額折扣、限時特價 |

什麼時候不該用?

⚠️過度設計警告

如果行為種類 ≤ 2 且不太可能增加,用 if-else 就夠清楚了。Strategy Pattern 會多出介面和類別,只有在行為真的會擴增時才值得用。具體判斷標準:行為種類固定為 1-2 種、每個分支邏輯不超過 3 行、未來一年內不會新增 → 不需要 Strategy。

執行流程

1

定義策略介面

宣告所有策略共用的方法簽名

2

實作具體策略

每種演算法各自實作介面

3

建立 Context

持有策略的參考,委派工作給策略

4

注入策略

在執行時傳入或切換具體策略

5

執行

Context 呼叫策略方法,不需要知道具體實作

流程解讀:Strategy 的套用從定義共用介面開始,這個介面規定了所有策略必須遵守的「合約」(方法簽名)。接著為每種演算法建立獨立的類別,各自實作介面。Context 是使用策略的主體,它只認識介面而不認識具體策略,因此可以在執行時透過建構子注入或 setter 方法切換策略,達到「開放封閉原則(Open-Closed Principle,指對擴展開放、對修改封閉)」的效果。

程式碼範例

C# 版本

csharp
// 1. 定義策略介面
public interface IAttackStrategy
{
    string Attack();
}
 
// 2. 具體策略
public class SwordAttack : IAttackStrategy
{
    public string Attack() => "揮劍砍下去!造成 10 點傷害";
}
 
public class BowAttack : IAttackStrategy
{
    public string Attack() => "射出一箭!造成 8 點傷害";
}
 
public class MagicAttack : IAttackStrategy
{
    public string Attack() => "發射火球!造成 15 點傷害";
}
 
// 3. Context:使用策略的角色
public class GameCharacter
{
    private IAttackStrategy _strategy;
 
    public GameCharacter(IAttackStrategy strategy)
    {
        _strategy = strategy;
    }
 
    public void SetStrategy(IAttackStrategy strategy)
    {
        _strategy = strategy;
    }
 
    public string PerformAttack() => _strategy.Attack();
}
 
// 4. 使用
var hero = new GameCharacter(new SwordAttack());
Console.WriteLine(hero.PerformAttack()); // 揮劍砍下去!
 
hero.SetStrategy(new MagicAttack());
Console.WriteLine(hero.PerformAttack()); // 發射火球!

TypeScript 版本

typescript
// 1. 定義策略介面
interface AttackStrategy {
  attack(): string;
}
 
// 2. 具體策略
class SwordAttack implements AttackStrategy {
  attack(): string {
    return "揮劍砍下去!造成 10 點傷害";
  }
}
 
class BowAttack implements AttackStrategy {
  attack(): string {
    return "射出一箭!造成 8 點傷害";
  }
}
 
class MagicAttack implements AttackStrategy {
  attack(): string {
    return "發射火球!造成 15 點傷害";
  }
}
 
// 3. Context
class GameCharacter {
  constructor(private strategy: AttackStrategy) {}
 
  setStrategy(strategy: AttackStrategy) {
    this.strategy = strategy;
  }
 
  performAttack(): string {
    return this.strategy.attack();
  }
}
 
// 4. 使用
const hero = new GameCharacter(new SwordAttack());
console.log(hero.performAttack()); // 揮劍砍下去!
 
hero.setStrategy(new MagicAttack());
console.log(hero.performAttack()); // 發射火球!

Python 版本

python
from abc import ABC, abstractmethod
 
# 1. 定義策略介面
class AttackStrategy(ABC):
    @abstractmethod
    def attack(self) -> str:
        pass
 
# 2. 具體策略
class SwordAttack(AttackStrategy):
    def attack(self) -> str:
        return "揮劍砍下去!造成 10 點傷害"
 
class BowAttack(AttackStrategy):
    def attack(self) -> str:
        return "射出一箭!造成 8 點傷害"
 
class MagicAttack(AttackStrategy):
    def attack(self) -> str:
        return "發射火球!造成 15 點傷害"
 
# 3. Context
class GameCharacter:
    def __init__(self, strategy: AttackStrategy):
        self._strategy = strategy
 
    def set_strategy(self, strategy: AttackStrategy):
        self._strategy = strategy
 
    def perform_attack(self) -> str:
        return self._strategy.attack()
 
# 4. 使用
hero = GameCharacter(SwordAttack())
print(hero.perform_attack())  # 揮劍砍下去!
 
hero.set_strategy(MagicAttack())
print(hero.perform_attack())  # 發射火球!

Java 版本

java
// 1. 定義策略介面
public interface AttackStrategy {
    String attack();
}
 
// 2. 具體策略
public class SwordAttack implements AttackStrategy {
    public String attack() { return "揮劍砍下去!造成 10 點傷害"; }
}
 
public class BowAttack implements AttackStrategy {
    public String attack() { return "射出一箭!造成 8 點傷害"; }
}
 
public class MagicAttack implements AttackStrategy {
    public String attack() { return "發射火球!造成 15 點傷害"; }
}
 
// 3. Context
public class GameCharacter {
    private AttackStrategy strategy;
 
    public GameCharacter(AttackStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void setStrategy(AttackStrategy strategy) {
        this.strategy = strategy;
    }
 
    public String performAttack() {
        return strategy.attack();
    }
}
 
// 4. 使用
GameCharacter hero = new GameCharacter(new SwordAttack());
System.out.println(hero.performAttack()); // 揮劍砍下去!
 
hero.setStrategy(new MagicAttack());
System.out.println(hero.performAttack()); // 發射火球!

結構圖

Client
uses
Context (GameCharacter)
delegates to
Strategy Interface
implements
ConcreteStrategy (Sword/Bow/Magic)

圖中 Client 透過 Context 來使用策略。Context(如 GameCharacter)持有 Strategy Interface 的參考,透過「委派(delegates to)」將實際工作交給策略物件。ConcreteStrategy(如 SwordAttack、BowAttack)各自實作介面,Client 完全不需要知道目前使用哪個具體策略。這種結構讓新增策略時只需新增一個類別,不需修改 Context 或 Client。

理解測驗

🤔 Strategy Pattern 的核心目的是什麼?

🤔 在 Strategy Pattern 中,誰負責決定用哪個策略?

🤔 以下哪個場景最適合用 Strategy Pattern?

面試常見問題

Q: Strategy Pattern 和 if-else 有什麼本質區別?何時該重構?

A: if-else 把所有邏輯集中在一個方法裡,每新增一種行為就要修改這個方法,違反開放封閉原則。Strategy 把每種行為封裝在獨立類別中,新增行為只需新增類別。重構的判斷標準:當 if-else 分支超過 3 個、每個分支邏輯超過 5 行、且未來確定會繼續增加分支時,就該重構為 Strategy。

Q: Strategy Pattern 和 Template Method Pattern 都能實現「可替換的行為」,怎麼選?

A: 關鍵差異在於「組合 vs 繼承」。Strategy 用組合(持有介面參考),可以在執行時動態切換策略;Template Method 用繼承(子類別覆寫方法),切換行為需要切換整個物件。選擇標準:如果行為需要在執行時動態切換 → Strategy;如果流程骨架固定、只有個別步驟需要自訂 → Template Method。

Q: 在 DI 容器中如何管理多個 Strategy?

A: 在 .NET 中可以用 IEnumerable<IStrategy> 注入所有策略,再用一個 Resolver 或 Factory 根據條件選擇。例如註冊 services.AddScoped<IPaymentStrategy, CreditCardPayment>() 等多個實作,再用 IPaymentStrategyResolver 根據使用者選擇回傳對應策略。

相關模式

| 模式 | 關係 | |------|------| | State | 結構幾乎相同(都是 Context + 介面 + 具體實作),但意圖不同:Strategy 的策略之間互不相識,由 Client 選擇;State 的狀態知道彼此,會自動轉換 | | Template Method | 都能實現可替換的行為,但 Strategy 用組合、Template Method 用繼承。口訣:「執行時換用 Strategy,編譯時換用 Template Method」 | | Factory Method | 常搭配使用 — 用 Factory 根據條件建立對應的 Strategy 實例 | | Command | Command 封裝的是「請求」(可排隊、撤銷),Strategy 封裝的是「演算法」(可替換)。兩者都是把行為物件化,但目的不同 |

重點整理

💡一句話記住

Strategy Pattern = 把「怎麼做」抽成介面,讓演算法像零件一樣即插即用。 口訣:「行為抽介面,切換不改碼」

| 概念 | 說明 | |------|------| | Strategy(策略介面) | 定義所有策略的共同方法 | | ConcreteStrategy(具體策略) | 各自實作不同的演算法 | | Context(上下文) | 持有策略參考,委派工作 | | 核心好處 | 開放封閉原則 — 新增策略不用改 Context | | 代價 | 多了介面和類別,簡單場景可能過度設計 |

你可能也想看

Singleton Pattern

按 ← → 鍵切換課程