Skip to content

設計模式

前言

為什麼你的程式碼總是「能跑但很亂」? 你可能遇到過這樣的情況:需求一變,程式碼就要大改;想複用一段邏輯,卻發現它和其他程式碼糾纏在一起。設計模式就是前人總結的「程式碼組織套路」,幫你寫出靈活、可維護的程式碼。

本章帶你理解最實用的設計模式,不是死記硬背,而是理解「什麼場景用什麼套路」。

這篇文章會帶你學什麼?

章節內容核心概念
第 1 章設計模式是什麼模式的本質與分類
第 2 章建立型模式如何優雅地建立物件
第 3 章結構型模式如何組織程式碼結構
第 4 章行為型模式如何管理物件間的互動

學完本章,你將掌握最常用的設計模式,能在實際專案中識別適用場景並靈活運用。


0. 全景圖:設計模式的本質

想像你在學做菜。你可以每次都從零開始摸索,也可以學習經典食譜——食譜不會限制你的創造力,反而讓你站在前人的肩膀上。設計模式就是程式設計世界的「經典食譜」。

設計模式的價值

  • 共同語言:說「這裡用觀察者模式」,團隊立刻理解你的設計意圖
  • 經驗複用:不用重新踩前人踩過的坑
  • 靈活擴展:好的模式讓程式碼面對變化時只需小改,而不是大改

透過下面的互動元件,瀏覽常見設計模式的分類和用途:

Design pattern catalog - click a category to view common patterns
🏗️Creational2 patterns
🧱Structural2 patterns
🎭Behavioral2 patterns

1. 建立型模式:如何優雅地建立物件

1.1 單例模式(Singleton)

場景:全域只需要一個實例,比如設定管理器、日誌記錄器、資料庫連線池。

javascript
class ConfigManager {
  static instance = null

  static getInstance() {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager()
    }
    return ConfigManager.instance
  }

  constructor() {
    this.config = {}
  }
}

// 無論呼叫多少次,都是同一個實例
const a = ConfigManager.getInstance()
const b = ConfigManager.getInstance()
console.log(a === b) // true

1.2 工廠模式(Factory)

場景:根據不同條件建立不同類型的物件,呼叫方不需要知道具體的建立細節。

javascript
function createNotification(type, message) {
  switch (type) {
    case 'email':
      return { send: () => console.log(`發送電子郵件: ${message}`) }
    case 'sms':
      return { send: () => console.log(`發送簡訊: ${message}`) }
    case 'push':
      return { send: () => console.log(`推送通知: ${message}`) }
    default:
      throw new Error(`未知通知類型: ${type}`)
  }
}

// 呼叫方不關心具體實作
const notification = createNotification('email', '你好')
notification.send()

2. 結構型模式:如何組織程式碼結構

2.1 轉接器模式(Adapter)

場景:兩個介面不相容,需要一個「轉換插頭」。比如舊 API 回傳的資料格式和新元件期望的格式不一致。

javascript
// 舊 API 回傳的格式
const oldApi = {
  getUserInfo: () => ({ user_name: '張三', user_age: 25 })
}

// 轉接器:轉換為新格式
function adaptUser(oldUser) {
  return { name: oldUser.user_name, age: oldUser.user_age }
}

const user = adaptUser(oldApi.getUserInfo())
// { name: '張三', age: 25 }

2.2 裝飾器模式(Decorator)

場景:在不修改原有程式碼的前提下,給物件添加新功能。像給手機套殼——手機功能不變,但多了保護。

javascript
// 基礎日誌函式
function log(message) {
  console.log(message)
}

// 裝飾:添加時間戳
function withTimestamp(fn) {
  return (message) => fn(`[${new Date().toISOString()}] ${message}`)
}

// 裝飾:添加日誌級別
function withLevel(fn, level) {
  return (message) => fn(`[${level}] ${message}`)
}

const enhancedLog = withTimestamp(withLevel(log, 'INFO'))
enhancedLog('服務啟動成功')
// [2025-01-15T10:30:00.000Z] [INFO] 服務啟動成功

3. 行為型模式:如何管理物件間的互動

3.1 觀察者模式(Observer)

場景:一個物件狀態變化時,需要自動通知其他物件。比如使用者下單後,需要同時發電子郵件、扣庫存、記日誌。

javascript
class EventEmitter {
  constructor() {
    this.listeners = {}
  }

  on(event, callback) {
    if (!this.listeners[event]) this.listeners[event] = []
    this.listeners[event].push(callback)
  }

  emit(event, data) {
    (this.listeners[event] || []).forEach(cb => cb(data))
  }
}

const bus = new EventEmitter()
bus.on('order:created', (order) => console.log('發送確認郵件', order.id))
bus.on('order:created', (order) => console.log('扣減庫存', order.id))
bus.emit('order:created', { id: 'ORD-001' })

3.2 策略模式(Strategy)

場景:同一個操作有多種演算法/策略,需要在執行時切換。比如不同的排序方式、不同的價格計算規則。

javascript
const pricingStrategies = {
  normal: (price) => price,
  vip: (price) => price * 0.8,
  svip: (price) => price * 0.6
}

function calculatePrice(price, memberLevel) {
  const strategy = pricingStrategies[memberLevel] || pricingStrategies.normal
  return strategy(price)
}

calculatePrice(100, 'vip')  // 80
calculatePrice(100, 'svip') // 60

透過下面的互動元件,動手體驗不同設計模式的執行效果:

🎮Design Pattern PlaygroundChoose a pattern and try it
Simulate event publish/subscribe: add subscribers, publish an event, and observe how notifications spread.
📡 Publisher
👥 Subscribers
No subscribers yet. Click "Add".

4. 如何選擇設計模式?

你遇到的問題推薦模式核心思路
全域只需一個實例單例控制實例數量
根據條件建立不同物件工廠封裝建立邏輯
介面不相容需要轉換轉接器包裝一層轉換
動態添加功能裝飾器層層包裝增強
狀態變化需通知多方觀察者發布-訂閱解耦
多種演算法需執行時切換策略將演算法封裝為物件

核心原則

設計模式不是越多越好。過度設計沒有設計一樣糟糕。只在真正需要靈活性的地方使用模式,簡單問題用簡單方案。記住 KISS 原則:Keep It Simple, Stupid。


5. AI 助力:用大模型學習和應用設計模式

大模型可以幫你識別程式碼中適合使用設計模式的場景,並給出具體的重構方案。

5.1 識別適用模式

提示詞

分析以下程式碼,判斷是否存在可以用設計模式改進的地方。
如果有,請說明:
1. 目前程式碼的問題
2. 推薦使用哪種設計模式
3. 重構後的程式碼範例
4. 為什麼這個模式適合這個場景

[貼上你的程式碼]

5.2 用具體場景學習模式

提示詞

用一個「外送點餐系統」的真實場景,分別示範以下設計模式的應用:
- 工廠模式:建立不同類型的訂單
- 觀察者模式:訂單狀態變化通知
- 策略模式:不同的外送費計算規則

用 JavaScript 程式碼範例,每個模式先展示不用模式的問題,
再展示用模式後的改進。

5.3 判斷是否過度設計

提示詞

審查以下程式碼,判斷是否存在過度設計的問題。
是否有不必要的抽象、用不到的設計模式、或過早的最佳化?
如果有,請建議如何簡化,遵循 KISS 原則。

[貼上你的程式碼]

AI 使用建議

讓 AI 用你熟悉的業務場景來解釋設計模式,比看抽象的 UML 圖有效得多。但記住:AI 可能傾向於推薦更複雜的方案,你需要自己判斷是否真的需要。


6. 總結

  1. 建立型模式:解決「如何建立物件」的問題,讓建立過程更靈活
  2. 結構型模式:解決「如何組織程式碼」的問題,讓結構更清晰
  3. 行為型模式:解決「物件間如何互動」的問題,讓協作更鬆耦合
  4. 靈活運用:根據實際場景選擇,不要為了用模式而用模式

終極思考

設計模式的本質是管理變化。好的設計讓變化的部分容易修改,不變的部分保持穩定。當你寫程式碼時問自己:「如果需求變了,我需要改多少地方?」——如果答案是「很多地方」,那可能需要一個設計模式來幫忙了。


延伸閱讀

  • 經典書籍:GoF《設計模式:可複用物件導向軟體的基礎》是設計模式的開山之作。
  • 現代視角:JavaScript 中很多模式因為語言特性(閉包、高階函式)變得更簡潔。
  • 實踐建議:先理解問題,再考慮模式。不要拿著鐵鎚找釘子。
  • 進階學習:了解 SOLID 原則,它是設計模式背後的指導思想。