意圖(Intent)#
將一個類別的介面轉換成客戶端期望的另一個介面。Adapter 讓原本因介面不相容而無法合作的類別能夠協同運作。
別名(Also Known As)#
Wrapper
動機(Motivation)#
假設一個繪圖編輯器定義了抽象類別 Shape,用來表示各種圖形物件。對於線條、多邊形等基本形狀,實作相對單純。但文字編輯功能牽涉到複雜的螢幕更新與緩衝區管理,若已有一個現成的 TextView 工具類別可用,理想做法是直接重用它。
問題在於:TextView 的介面與 Shape 不相容。我們不應(也可能無法)修改 TextView 的原始碼來迎合特定應用的介面需求。
解決方案是定義一個 TextShape 作為 adapter,將 TextView 的介面適配到 Shape 的介面。這可以透過兩種方式達成:
- Class Adapter:透過多重繼承,同時繼承 Shape 的介面與 TextView 的實作
- Object Adapter:在 TextShape 內組合一個 TextView 實例,以委派方式實作 Shape 介面
Adapter 不僅僅是轉換介面。它還可以補充被適配類別缺少的功能。例如 TextShape 可以自行實作 Shape 要求的
CreateManipulator操作,提供 TextView 原本不支援的拖曳功能。
適用場景(Applicability)#
- 想重用一個既有類別,但其介面與需求不符
- 想建立可重用的類別,能與介面不相容的未知類別合作
- (僅限 Object Adapter) 需要適配多個既有子類別,逐一建立子類別來適配不切實際
結構(Structure)#
Adapter 模式有兩種形式:
- Class Adapter 透過多重繼承,同時繼承 Target 的介面與 Adaptee 的實作
- Object Adapter 持有 Adaptee 的引用,透過物件組合進行介面轉換
classDiagram
class Target {
+Request()
}
class Adaptee {
+SpecificRequest()
}
class Adapter {
+Request()
}
class Client
Target <|-- Adapter
Adaptee <|-- Adapter
Client --> Target
note for Adapter "Class Adapter:\n多重繼承 Target 與 Adaptee"classDiagram
class Target {
+Request()
}
class Adaptee {
+SpecificRequest()
}
class Adapter {
-adaptee
+Request()
}
class Client
Target <|-- Adapter
Adapter o--> Adaptee
Client --> Target
note for Adapter "Object Adapter:\n組合 Adaptee 實例"參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Target | Shape | 定義客戶端使用的領域特定介面 |
| Client | DrawingEditor | 透過 Target 介面與物件互動 |
| Adaptee | TextView | 擁有需要被適配的既有介面 |
| Adapter | TextShape | 將 Adaptee 介面適配到 Target 介面 |
協作方式(Collaborations)#
- Client 呼叫 Adapter 的操作,Adapter 再轉呼叫 Adaptee 的對應操作來完成請求
優缺點(Consequences)#
Class Adapter 的特性:
- 綁定到具體的 Adaptee 類別,無法適配其子類別
- 可以覆寫 Adaptee 的行為(因為是子類別關係)
- 只引入一個物件,不需要額外的指標間接層
Object Adapter 的特性:
- 一個 Adapter 可以與多個 Adaptee(及其子類別)合作
- 較難覆寫 Adaptee 行為,需要額外的子類別化
其他考量:
- 適配程度的光譜:從簡單的操作名稱轉換,到支援完全不同的操作集合,工作量取決於 Target 與 Adaptee 介面的相似程度
- Pluggable Adapter:將介面適配內建於類別中,讓類別能在不同介面預期下被重用。可透過抽象操作、委派物件或參數化方式實現
- Two-way Adapter:透過多重繼承同時實作兩個介面,使物件在兩個系統中都能被透明使用
設計 pluggable adapter 時,關鍵是找出「最窄介面」(narrow interface)—— 最小的操作子集即可完成適配。介面越窄,適配越容易。
實作要點(Implementation)#
- C++ 的 class adapter:公開繼承 Target,私有繼承 Adaptee,使 Adapter 成為 Target 的子型別但不是 Adaptee 的子型別
- Pluggable adapter 的三種實作策略:
- 使用抽象操作:在 Target 中定義抽象方法,子類別實作適配邏輯
- 使用委派物件(delegate):Target 將請求轉發給可替換的委派物件
- 參數化適配器:透過函式物件(如 Smalltalk 的 block)參數化每個請求的適配方式
已知應用(Known Uses)#
- ET++Draw:使用 TextShape adapter 重用 ET++ 的文字編輯類別
- InterViews 2.6:GraphicBlock 作為 object adapter,將 Graphic 介面適配到 Interactor 介面
- ObjectWorks\Smalltalk:PluggableAdaptor 將應用物件適配到 ValueModel 介面;TableAdaptor 將物件序列適配為表格呈現
- NeXT AppKit:使用 delegate 物件進行介面適配
- Meyer 的「Marriage of Convenience」:FixedStack 透過 class adapter 將 Array 實作適配到 Stack 介面
相關模式(Related Patterns)#
- Bridge:結構類似 object adapter,但意圖不同。Bridge 在設計初期用來分離介面與實作;Adapter 用來改變既有物件的介面
- Decorator:不改變介面而增強功能,對應用程式更透明。Decorator 支援遞迴組合,純粹的 Adapter 則不行
- Proxy:為另一個物件提供代理,不改變其介面