設計模式
前言
為什麼你的程式碼總是「能跑但很亂」? 你可能遇到過這樣的情況:需求一變,程式碼就要大改;想複用一段邏輯,卻發現它和其他程式碼糾纏在一起。設計模式就是前人總結的「程式碼組織套路」,幫你寫出靈活、可維護的程式碼。
本章帶你理解最實用的設計模式,不是死記硬背,而是理解「什麼場景用什麼套路」。
這篇文章會帶你學什麼?
| 章節 | 內容 | 核心概念 |
|---|---|---|
| 第 1 章 | 設計模式是什麼 | 模式的本質與分類 |
| 第 2 章 | 建立型模式 | 如何優雅地建立物件 |
| 第 3 章 | 結構型模式 | 如何組織程式碼結構 |
| 第 4 章 | 行為型模式 | 如何管理物件間的互動 |
學完本章,你將掌握最常用的設計模式,能在實際專案中識別適用場景並靈活運用。
0. 全景圖:設計模式的本質
想像你在學做菜。你可以每次都從零開始摸索,也可以學習經典食譜——食譜不會限制你的創造力,反而讓你站在前人的肩膀上。設計模式就是程式設計世界的「經典食譜」。
設計模式的價值
- 共同語言:說「這裡用觀察者模式」,團隊立刻理解你的設計意圖
- 經驗複用:不用重新踩前人踩過的坑
- 靈活擴展:好的模式讓程式碼面對變化時只需小改,而不是大改
透過下面的互動元件,瀏覽常見設計模式的分類和用途:
1. 建立型模式:如何優雅地建立物件
1.1 單例模式(Singleton)
場景:全域只需要一個實例,比如設定管理器、日誌記錄器、資料庫連線池。
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) // true1.2 工廠模式(Factory)
場景:根據不同條件建立不同類型的物件,呼叫方不需要知道具體的建立細節。
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 回傳的資料格式和新元件期望的格式不一致。
// 舊 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)
場景:在不修改原有程式碼的前提下,給物件添加新功能。像給手機套殼——手機功能不變,但多了保護。
// 基礎日誌函式
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)
場景:一個物件狀態變化時,需要自動通知其他物件。比如使用者下單後,需要同時發電子郵件、扣庫存、記日誌。
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)
場景:同一個操作有多種演算法/策略,需要在執行時切換。比如不同的排序方式、不同的價格計算規則。
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透過下面的互動元件,動手體驗不同設計模式的執行效果:
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. 總結
- 建立型模式:解決「如何建立物件」的問題,讓建立過程更靈活
- 結構型模式:解決「如何組織程式碼」的問題,讓結構更清晰
- 行為型模式:解決「物件間如何互動」的問題,讓協作更鬆耦合
- 靈活運用:根據實際場景選擇,不要為了用模式而用模式
終極思考
設計模式的本質是管理變化。好的設計讓變化的部分容易修改,不變的部分保持穩定。當你寫程式碼時問自己:「如果需求變了,我需要改多少地方?」——如果答案是「很多地方」,那可能需要一個設計模式來幫忙了。
延伸閱讀
- 經典書籍:GoF《設計模式:可複用物件導向軟體的基礎》是設計模式的開山之作。
- 現代視角:JavaScript 中很多模式因為語言特性(閉包、高階函式)變得更簡潔。
- 實踐建議:先理解問題,再考慮模式。不要拿著鐵鎚找釘子。
- 進階學習:了解 SOLID 原則,它是設計模式背後的指導思想。