軟體設計

Adapter・Facade・Proxy・Decorator 的差異

Adapter 用於轉換介面,Facade 用於隱藏複雜度,Proxy 用於控制存取,Decorator 用於新增功能。本文依照意圖與選擇標準,比較四種結構相似的包裝模式。

閱讀 5 分鐘
Adapter・Facade・Proxy・Decorator 的差異 封面圖

包裝模式四兄弟,為什麼這麼容易搞混?

學習設計模式時,總會遇到一個讓人卡住的地方。

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 是「摘要」介面。


總結

區分四兄弟,最後只需要問:「為什麼要包裝?」是要改變、隱藏、控制,還是新增介面功能?

只要問這個問題,面對程式碼時就不會再搞混。請把今天的比較表存下來,需要時拿出來參考。

延伸閱讀