為 Pattern 而 Pattern
看完 GoF 之後,看什麼都想套一個 Pattern。UserService 需要一個 UserServiceFactory,但 UserService 從來只有一種實作——這個 Factory 什麼問題都沒解決,只是增加了一層間接。
Pattern 存在的理由是解決一個具體的設計問題——靈活性、擴展性、解耦。如果問題不存在,Pattern 就不應該存在。
Singleton 到處塞
Singleton 是最常被濫用的 Pattern,因為它用起來最簡單(一個 static 方法就搞定)。
常見的 Singleton 誤用:把整個 Database Connection、Config、UserRepository 都做成 Singleton,然後在 class 裡直接 Database.getInstance() 存取。
問題:
- 測試無法替換:測試時無法用 fake 替換,必須連真實 DB
- 隱藏依賴:
UserService的依賴不在建構子裡,看 class 定義看不出來它依賴什麼 - Global state:多個 Singleton 互相依賴,初始化順序複雜
修法:DI(Dependency Injection)——把依賴顯式傳入,DI Container 管理 singleton 的生命週期。
把 Strategy 和 State 搞混
兩個 Pattern 都用 interface + 多態替換行為,但解決的問題不同:
- Strategy:外部選擇算法(排序用快排還是合併排序,由呼叫者決定),物件本身不知道也不在乎
- State:物件自己根據狀態改變行為(訂單在不同狀態下,
cancel()的行為不同),狀態轉換是物件內部邏輯
把 State 的 transition logic 放到外部(Strategy 的做法),讓呼叫者決定什麼時候切換狀態,違反了封裝。
Builder 用在簡單物件
Builder Pattern 解決的問題是:有很多可選參數、建構過程複雜、需要保證物件完整性。
// 這種 Builder 是純粹的繁複
Order order = new OrderBuilder()
.setId(1)
.setProduct(product)
.build();
// 等同於:new Order(1, product)三個以下的參數,用建構子或工廠方法就夠。Builder 適合參數超過 5 個、多個可選參數、或建構步驟有驗證邏輯的場景。
Observer 沒有 Unsubscribe,記憶體洩漏
// 常見的記憶體洩漏
class UserPage extends Component {
componentDidMount() {
eventBus.on('user:updated', this.handleUpdate)
// 忘記在 componentWillUnmount 呼叫 off()
}
// ❌ 沒有 unsubscribe
}Component 被 unmount 後,事件系統仍然持有 handler 的引用,導致 Component 無法被 GC,而且 handleUpdate 還可能在一個已被銷毀的 component 上執行。
修法:每個 on() 對應一個 off(),用 WeakReference 或語言的自動清理機制(React useEffect 的 cleanup function)。
Proxy 和 Decorator 混淆
兩個 Pattern 的結構相似(都有一個 wrapper 包住真實物件),但語意不同:
- Proxy:控制對真實物件的存取——lazy initialization、access control、logging、remote proxy
- Decorator:動態增加真實物件的功能——在不修改類別的情況下增加行為
當你想要「限制誰能呼叫」用 Proxy,當你想要「在呼叫前後加東西」用 Decorator。把存取控制放在 Decorator 裡、把功能增加放在 Proxy 裡——功能上可能一樣能跑,但語意混淆讓維護者困惑。
Pattern 的第一原則
Pattern 是問題的解法,不是程式碼的裝飾。
識別問題 → 找匹配的 Pattern,而不是學了 Pattern → 找問題套。這個順序反了,Pattern 就成了反效果。