設計模式:策略模式 (Strategy)

7 分鐘
軟體設計設計模式OOP

Strategy 把一系列演算法封裝成可互換的物件。Context 在執行期使用被插進來的那一個策略,而不需要知道它內部是怎麼運作的。


意圖與用途

策略模式最常解決的問題是:一個類別針對同一個操作有多種變體,而它用 switch-case 或 if-else 在這些變體之間做選擇。

Strategy 把每一種變體搬進各自的類別,藏在同一個介面後面。Context 持有一個指向策略的引用,把工作委派給它,而完全不知道背後的實作。


結構與角色

  • Strategy:所有演算法共同的介面
  • ConcreteStrategy:各個具體的演算法實作
  • Context:持有一個 Strategy 引用,對外提供統一的進入點

實作範例:購物車排序

TypeScript
interface SortStrategy {
  sort(items: T[]): T[];
}

// 依價格由低到高
class SortByPriceAsc implements SortStrategy {
  sort(items: Product[]): Product[] {
    return [...items].sort((a, b) => a.price - b.price);
  }
}

// 依價格由高到低
class SortByPriceDesc implements SortStrategy {
  sort(items: Product[]): Product[] {
    return [...items].sort((a, b) => b.price - a.price);
  }
}

// 依評分由高到低
class SortByRating implements SortStrategy {
  sort(items: Product[]): Product[] {
    return [...items].sort((a, b) => b.rating - a.rating);
  }
}

// 依名稱字母順序
class SortByName implements SortStrategy {
  sort(items: Product[]): Product[] {
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }
}

// Context
class ShoppingCart {
  constructor(
    private items: Product[],
    private sortStrategy: SortStrategy,
  ) {}

  setStrategy(strategy: SortStrategy): void {
    this.sortStrategy = strategy;
  }

  getSortedItems(): Product[] {
    return this.sortStrategy.sort(this.items);
  }
}

// 執行期切換策略
const cart = new ShoppingCart(products, new SortByPriceAsc());
console.log(cart.getSortedItems());

cart.setStrategy(new SortByRating());
console.log(cart.getSortedItems());

新增一種排序方式,只需要實作一個新的 SortStrategyShoppingCart 完全不必動。


適用情境

適用時機

  • 一個操作有多種變體,目前是用 switch-case 或 if-else 在切換
  • 想把複雜的演算法封裝起來,讓它們彼此獨立
  • 需要在執行期動態切換行為

策略模式與 OCP

策略模式是實現 OCP 最直接的方式之一:用新增策略類別來擴充,永遠不去修改 Context。


總結

Strategy 是實務上最常用到的模式之一。每當你把一個函式當成參數傳進去、藉此客製內部行為時,其實你已經在用 Strategy 的函式版等價形式了。

而當邏輯複雜到單純一個函式不夠用時,搭配完整類別階層的 Strategy,能給你同樣的彈性,又多了一份結構。