意圖(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)#

參與者範例職責
TargetShape定義客戶端使用的領域特定介面
ClientDrawingEditor透過 Target 介面與物件互動
AdapteeTextView擁有需要被適配的既有介面
AdapterTextShape將 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:為另一個物件提供代理,不改變其介面