---
# System prepended metadata

title: 策略模式 (Strategy)
tags: [JavaScript, 設計模式, JavaScript 設計模式與開發實踐]

---

# 策略模式 (Strategy)

單例模式的定義是：

> _定義一系列的演算法，把它們一個個**封裝**起來，並且使它們**可以相互替換**。_

現實中，很多時候也是有多種途徑到達同一個目的地。
比如我們可以根據自己的經濟狀況選擇買可以負擔得起的手機。

- 石油王選 iphone12。
- 小康選 iphoneSE。
- 吃土選 Nokia3310。

![為所欲為](https://5b0988e595225.cdn.sohucs.com/images/20180813/0ba7bb6faa984cefb447931162a6ece6.gif =500x)

### 使用策略模式計算獎金

以下是一個直覺的計算獎金函數 `calculateBonus` 。

```javascript
/**
 * 計算獎金數額
 * @param {string} performanceLevel - 工資數額
 * @param {number} salary - 績效考核等級
 * @return {number}
 */
function calculateBonus (performanceLevel, salary) {
  switch (performanceLevel) {
    case: 'S': return salary * 4
    case: 'A': return salary * 3
    case: 'B': return salary * 2
  }
}

console.log(calculateBonus('B', 20000)) // 40000
console.log(calculateBonus('S', 6000)) // 24000
```

這段程式碼隱含以下缺點：

- `calculateBonus` 函數比較龐大，包含太多 if-else 類的述句，這些述句需要涵蓋所有的邏輯分支。
- `calculateBonus` 函數缺乏彈性，如果要把績效的參數做修正，只能深入 `calculateBonus` 內部實作，違反「**開放－封閉原則**」
- 演算法的重用性差，如果專案有其他地方需要**重用**計算獎金的邏輯，他們只有用複製貼上一途。

我們可以使用策略模式重構程式碼，目的是將**演算法的使用**(不變)和**演算法的實作**(變化)分離開來。

我們試著先用 class 語法糖模仿強型別語言的實作。
將計算績效的規則封裝在對應的策略類別中：

```javascript
class performaceS {
  calculate(salary) {
    return salary * 4
  }
}

class performaceA {
  calculate(salary) {
    return salary * 3
  }
}

class performaceB {
  calculate(salary) {
    return salary * 2
  }
}
```

接下來定義獎金類別 `Bonus` ：

```javascript
class Bonus {
  constructor() {
    this.salary = null
    this.strategy = null
  }
  setSalary(salary) {
    this.salary = salary
  }
  setStrategy(strategy) {
    this.strategy = strategy
  }
  getBonus() {
    // 注意！Bonus本身沒有能力進行計算
    // 把計算獎金的操作委託給對應的策略物件
    return this.stragegy.calculate(this.salary)
  }
}
```

試著使用重構完畢的程式碼，將**演算法實作物件**餵給**演算法執行物件**進行計算：

```javascript
const bonus = new Bonus()

bonus.setSalary(20000)
bonus.setStrategy(new PerformanceB())
console.log(bonus.getBonus()) // 40000

bonus.setSalary(6000)
bonus.setStrategy(new PerformanceB())
console.log(bonus.getBonus()) // 24000
```

經過重構後，每個類別職責變得更加鮮明清晰。

### JavaScript 版本的策略模式

在 JavaScript 中，**函數也是物件**，寫起來**超 ♂︎ 自 ♂︎ 由**。
可以更簡單和直接的把每個策略定義為函數，也不需要額外實體化實例來執行策略，結構上非常簡潔又看得舒服。

![那個自由的男人](https://media1.tenor.com/images/38e5262d801737950f5204669aeff197/tenor.gif?itemid=13534337)

```javascript
const strategy = {
  S: function (salary) {
    return salary * 4
  },
  A: function (salary) {
    return salary * 3
  },
  B: function (salary) {
    return salary * 2
  },
}

function calculateBonus(level, salary) {
  return strategy[level](salary)
}

console.log(calculateBonus('B', 20000)) // 40000
console.log(calculateBonus('S', 6000)) // 24000
```

由於現在的 JavaScript 現在也支持 `import` 及 `export` 的語法，我們也可以不借助 `strategy` 物件包裝演算法，讓策略模式變得更加隱形，做為**高階函數**將演算法按需載入。

```javascript
// strategy.js
export function S(salary) {
  return salary * 4
}
export function A(salary) {
  return salary * 3
}
export function B(salary) {
  return salary * 2
}
export function calculateBonus(fn, salary) {
  return fn(salary)
}
```

### 表單校驗

### 策略模式的優缺點

### 小結

###### tags: `JavaScript 設計模式與開發實踐` `設計模式` `JavaScript`
