包裝模式四兄弟,為什麼這麼容易搞混?
學習設計模式時,總會遇到一個讓人卡住的地方。
Adapter、Facade、Proxy、Decorator。這四個模式很容易混在一起。
你可能會想:「不就都是包裝另一個物件嗎?」確實,四者都在內部持有另一個物件,並站在它前面,因此外觀看起來很相似。
所以即使記住了名稱,實際寫程式碼時仍常常分不清楚。
先說重點。
四種模式都會包裝物件,但包裝的「目的」不同。
Adapter 會改變介面,Facade 會隱藏複雜度,Proxy 會控制存取,Decorator 會新增功能。
記住這一句,其他內容就會順著理解。本文會清楚拆解這四兄弟,讓你之後能在程式碼中準確辨識它們。
比較之後若需要實際實作,可以接著閱讀 使用 Swift Adapter 包裝 Legacy API 與 使用 Swift Facade 簡化子系統。
四兄弟一覽比較表
在用文字說明前,先看表格吧。我以前會把這張表貼在桌前,搞混時就看一眼。
| 模式 | 目的 | 介面 | 包裝對象 |
|---|---|---|---|
| Adapter | 讓不相容的介面彼此適配 | 改變 | 通常 1 個 |
| Facade | 將複雜的多個子系統整合成簡單入口 | 重新建立 | 多個 |
| Proxy | 控制、延遲或快取對原物件的存取 | 相同 | 1 個 |
| Decorator | 在原物件上附加功能 | 相同 | 1 個 |
最重要的判斷標準是:「介面是否維持原樣?」
Proxy 和 Decorator 的介面都與原物件相同,因此使用端不必知道物件被包裝了。
相反地,Adapter 會刻意改變介面,而 Facade 則會直接建立新的入口。
Adapter 與 Decorator 有什麼不同?
最容易搞混的就是這一對。用程式碼來看,差異會非常明顯。
Adapter 是讓「不相容的東西彼此配合」的插頭轉接器,就是讓 220V 裝置接上 110V 插座的那種 Adapter。
// 現有函式庫只有 legacyPrint(text:)
// 當我們的程式碼期待 draw()時
protocol Renderer { func draw() }
struct LegacyLabel { func legacyPrint(_ t: String) { print(t) } }
struct LabelAdapter: Renderer {
let legacy: LegacyLabel
func draw() { legacy.legacyPrint("轉換後的呼叫") }
}
LabelAdapter(legacy: LegacyLabel()).draw()
// 輸出:轉換後的呼叫
重點是介面從 legacyPrint 改成了 draw。
Decorator 維持介面不變,只疊加功能,就像替咖啡加一份濃縮。
protocol Coffee { func cost() -> Int }
struct Americano: Coffee { func cost() -> Int { 4000 } }
struct ShotDecorator: Coffee {
let base: Coffee
func cost() -> Int { base.cost() + 500 }
}
ShotDecorator(base: Americano()).cost()
// 輸出: 4500
兩者都持有 base,但 Adapter 會改變名稱,Decorator 則會擴充行為。目的完全不同。
Proxy 與 Facade 呢?
Proxy 的介面與原物件完全相同,但會在中間進行控制。
以載入圖片為例,它不會立即載入真正的圖片,而是等到需要時才載入。這就是延遲載入。
快取、權限檢查和記錄日誌也常由 Proxy 負責。使用端以為自己呼叫的是原物件,實際上呼叫的是代理人。
Facade 的性質不太一樣。它不是包裝一個物件,而是把多個物件藏在後面。
以家庭劇院為例,可以把開啟投影機、開啟喇叭、調暗燈光和開始播放的流程,整合成一個 watchMovie() 呼叫。不必了解內部細節,按一個按鈕就能使用。
總結來說,Proxy 是「同一扇門前的門衛」,Facade 則是「代替你開啟多扇門的服務台」。
什麼時候該用,什麼時候該避免?
模式是工具,應該依情境使用。硬套模式反而會讓程式碼更複雜。
| 情境 | 判斷 |
|---|---|
| 外部函式庫的介面不相容 | Adapter |
| 想用簡單入口操作多個複雜模組 | Facade |
| 需要存取控制、延遲載入或快取 | Proxy |
| 想彈性地組合並附加功能 | Decorator |
當然也有明確應該避免的時候。
- 明明沒有包裝的理由,卻只因為「想用過這個模式」而加入,最後只會增加層級。
- Decorator 疊得太多,除錯時會完全追不到值在哪裡改變,簡直成了地獄。
- Facade 承擔太多責任,自己也會變成巨大的單體。
我會問自己:「現在包裝的目的,是上面四個中的哪一個?」如果答不出來,就移除這個模式。
面試時會這樣問
Q. Proxy 和 Decorator 都用相同介面包裝物件,兩者有什麼不同?
A. 目的不同。Proxy 的目的在於控制、延遲或快取對原物件的存取,原物件本身的功能維持不變。Decorator 則是在保留原物件介面的同時附加功能。可以把它理解成:Proxy 決定「要不要呼叫」,Decorator 決定「呼叫之後還要做什麼」。
Q. Adapter 和 Facade 的差異呢?
A. Adapter 通常包裝單一對象,讓不相容的介面彼此適配;Facade 則包裝多個子系統,提供簡單的新入口。Adapter 是「翻譯」介面,Facade 是「摘要」介面。
總結
區分四兄弟,最後只需要問:「為什麼要包裝?」是要改變、隱藏、控制,還是新增介面功能?
只要問這個問題,面對程式碼時就不會再搞混。請把今天的比較表存下來,需要時拿出來參考。

