軟體設計

為什麼需要設計模式,又為什麼不能盲目信奉?(實務整理)

「設計模式現在還需要學嗎?」這個問題在開發者社群中相當常見。

閱讀 5 分鐘
為什麼需要設計模式,又為什麼不能盲目信奉?(實務整理) 封面圖

「設計模式現在還需要學嗎?」這個問題在開發者社群中相當常見。

有人說「因為是基本功,所以一定要學會」,也有人說「現在的語言已經不需要其中一半」。兩邊都有道理,反而讓人更困惑。

先說結論:設計模式非常值得學習。但從「套用模式」本身成為目的的那一刻起,它反而會變成破壞程式碼的捷徑。

本文將依序說明設計模式為什麼必要,以及為什麼不能盲目信奉。


什麼是設計模式

設計模式簡單來說,就是解決經常重複出現之設計問題的驗證過解法目錄

起點是 GoF(Gang of Four)在 1994 年的《Design Patterns》中整理出 23 種模式。Singleton、Factory、Observer、Strategy 等名稱都源自這本書。

重要的是,這些模式並不是某個人憑空發明的。開發者觀察到無數專案中有人以相似方式解決類似問題,接著替這些做法命名並整理成目錄

因此,設計模式的本質與其說是「新技術」,不如說更接近「整理過的經驗」。


為什麼需要 1 — 團隊的共用詞彙

設計模式最大的實務價值不在程式碼,而在溝通

與其解釋「這個類別只維持一個執行個體、讓它能從全域存取,並把初始化延後到第一次存取」,不如一句「用 Singleton 吧」就能說清楚。

想想看,程式碼審查中一句「這裡用 Observer 模式不是更好嗎?」包含了多少資訊。模式名稱是壓縮率極高的語言。

不了解這套詞彙,閱讀團隊對話、技術文件與開源程式碼註解的速度都會變慢。技術面試不斷詢問設計模式,原因也在這裡。


為什麼需要 2 — 閱讀框架的關鍵

我們每天使用的框架,本來就是設計模式的集合。

  • iOS 的UITableViewDelegateDelegate 模式
  • NotificationCenter而 Combine 的 publisher 是Observer 模式
  • SwiftUI 的View協定組合接近Composite 模式
  • URLSession.sharedSingleton

了解模式後,即使第一次看到框架 API,也能反向讀出設計意圖:「啊,原來是這種結構。」你不只是讀文件的時間變短,還能預測文件沒有寫出的部分。

由 Delegate、Observer、Singleton、Factory、Strategy 設計模式方塊組合而成的 iOS App 畫面插圖
Delegate、Observer、Singleton……每天使用的框架早已是模式的組合品

為什麼需要 3 — 重複使用驗證過的解法

從頭思考並解決相同問題,和從數十年來持續打磨的解法出發,起跑點並不相同。

例如,「物件建立邏輯散落各處,每次修改都會漏掉某些地方」是 Factory 模式處理過的問題;「狀態每次改變都要更新多個畫面」則是 Observer 模式處理過的問題。

模式整理的不只是解法,還包括該解法的取捨。「Singleton 是全域狀態,因此會讓測試變困難」這類副作用清單也是模式的一部分。就像地圖上標示了前人踩過的地雷位置。


但是,為什麼不能盲目信奉?

看到這裡,設計模式似乎無所不能。問題往往發生在「剛學會模式之後」。

手裡拿著鐵鎚,什麼都像釘子

學會模式後,會想把它套用到任何地方,這就是著名的**黃金鐵鎚(golden hammer)**陷阱。

在讀取一個設定值的程式碼上套 Abstract Factory,為只有兩個分支的邏輯導入 Strategy 模式,把一個類別就能完成的工作拆成 3 個介面與 4 個實作類別。

模式是用來降低複雜度的工具,但用在不夠複雜的問題上時,模式本身就會成為新的複雜度。如果第一次看程式碼的人必須跨過 6 個檔案才能找到實際邏輯,那就不是設計,而是迷宮。

語言發展後,模式會消失

GoF 的 23 種模式是以 1994 年的 C++ 與 Smalltalk 為前提整理的,其中不少已被語言功能吸收。

// 1994年代:Strategy 模式 — 協定 + 實作類別
protocol SortStrategy {
    func sort(_ numbers: [Int]) -> [Int]
}
final class AscendingSort: SortStrategy {
    func sort(_ numbers: [Int]) -> [Int] { numbers.sorted(by: <) }
}

// 現在 Swift: 只要一個閉包就能達成相同目的
let sorted = numbers.sorted(by: >)

在能將函式當作值傳遞的語言中,多數 Strategy 與 Command 模式用一行閉包就能完成。Swift 的enum、值型別與協定預設實作,也能在語法層級解決過去需要用模式處理的問題。

也可以把模式看成「語言還做不到的事,由人用結構補足」。因此,若把模式清單當成跨時代不變的正解來背誦,就會用數十年前的方法解決語言早已處理好的問題。

模式成為目的的那一刻

最危險的訊號,是設計討論不是從「如何解決這個問題」出發,而是從「要使用哪個模式」出發。

模式比較像解決問題時抵達的終點,而不是起點。重構相關文獻也建議:「不要一開始就套入模式,等程式碼受到朝該方向演進的壓力時,再重構成模式。」

設計模式套用判斷流程圖 — 檢視問題的重複性與取捨,選擇維持簡單或重構為模式
模式不是起點,而更接近產生壓力時才會抵達的終點

那麼,該如何使用?

總結來說,平衡點如下。

學習時,先看問題,再看解法。 必須記住每個模式是在「什麼情況下」出現的,才能在情況不符時選擇不用。學習模式有一半是在學「什麼時候不該使用」。

套用時,從最簡單的程式碼開始。 當重複出現 3 次、真的收到變更需求,而且目前的結構開始難以負荷時,再重構為模式也不遲。為了未來的彈性而在今天購買複雜度,多半是不划算的交易。

閱讀時,請積極運用。 閱讀別人的程式碼、框架與開源專案時,模式知識是沒有副作用的純收益。盲信的風險出現在「使用時」,而不是「閱讀時」。


總結

  • 設計模式是解決重複設計問題的驗證過的解法目錄,是觀察的產物,而不是發明。
  • 必要原因:團隊的共用詞彙、閱讀框架設計的關鍵,以及連取捨都整理好的驗證過解法
  • 不能盲信的原因:套用在簡單問題上會讓模式本身變成複雜度(黃金鐵鎚);許多模式會因語言發展而消失;模式成為目的時,設計就會本末倒置。
  • 實務原則:從問題出發,以簡單程式碼開始,產生壓力時再重構為模式。閱讀時則可以放心運用。

延伸閱讀