譯-設計模式-結構模式之Adapter

翻譯整理自:https://refactoring.guru/design-patterns/adapter

更多請移步: 我的博客

目的

適配器是一種結構設計模式,使得不兼容接口間的可正常進行協作。

問題

想象你有一個使用XML作爲數據處理格式的APP,但你用到了一個僅支持JSON數據格式的類庫。

舉個例子,你有一個做股票數據的APP。他從多個數據源獲取XML來展示成圖標。在一個新版本中,你決定使用一些三方的分析包。但是有一個問題:分析包僅支持JSON數據。

xmlcant2json.png

在這種情況下,可以重寫你自己的代碼以便支持JSON或者改變依賴的庫使其支持XML。第一種選擇要破壞已有的代碼,第二種選擇看上去是不可能的,因爲我們常常無法修改三方庫。

解決方法

你可以創建一個適配器。它可以將調用方發送的數據按照格式轉換成三方庫可以解析的類型。適配器封裝了對一個對象複雜的轉換過程。

適配器可以不僅可以格式化數據,也可以適配接口。比如,適配器接收到一個對方法A的調用,他可以轉交給被包裝的方法B、C、D。

有時甚至可以創建一個雙向適配,這樣就可以雙向轉換。

xml2json

綜上討論,股票超市APP需要一個特別的XML_To_JSON_Adapter類。在調用分析類庫之前,將xml轉換爲json。採用這種方式你將不需要改變任何已有的APP代碼,也不需要改變分析庫的代碼。

真實世界的類比

不同國家的插頭和插板

當你第一次從美國到偶主,你會發現沒辦法給你的筆記本充電。兩個國家插座和插頭的標準根本不一樣。這就是爲什麼美國的插頭沒辦法適配德國的插座。

這個問題可以通過使用具有美式插座和歐式插頭的電源插頭適配器來解決。

結構

對象適配

實現採用組合原則:適配器實現其中一對象的接口,並且封裝另一個對象。他可以在所有新生代語言中實現。

objectAdapter

類適配

實現採用集成。適配器同是繼承兩個接口。該方式只有像C++這種支持多繼承的語言可以實現。

classAdapter

  1. Existing interface (存在的接口或者類)已經被你的其餘代碼支持

  2. Service (服務)是一些不能在應用類直接工作的有用類(通常是三方庫或者遺留代碼)

  3. Adapter 實現了 Existing interface 並且持有 Service 類引用。

    adapter會接收到client通過 Existing interface 定義的方法調用。他在調用 Service 前可能會修正調用參數的類型或者格式化數據。

  4. Client 使用 Adapter 調用 Existing interface 中定義的接口。

    這裏允許添加新的 Adapter 來編程而不需要改動已經存在的代碼(Service 改變的的情況可能也會出現,比如,當你更新依賴的三方庫)。

  5. 這個adapter類不需要封裝任何對象。他同時實現了兩個接口。因此,對所有的對象都是兼容的。

僞代碼

讓我們看看Adapter如何從一個接口到另一個接口進行基本數據轉換。這個例子給予圓孔和方形釘的衝突。圓孔可以和圓釘很好的工作;可以通過兩者的半徑來決定是否合適。但是方形釘不能測量半徑。

pseudocode

這就是我們爲什麼需要創建一個Adapter類來封裝方形釘對象並且僞裝他有一個等於方形直徑一半的半徑。

// Classes with compatible interfaces: RoundHole and RoundPeg.
class RoundHole is
    constructor RoundHole(radius) { ... }
    method getRadius
    method fits(peg: RoundPeg) is
        return this.getRadius() >= peg.radius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
        Return the peg radius.


// Obsolete incompatible class: SquarePeg.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
        Return the square peg width.


// Adapter allows fitting square pegs into round holes.
class SquarePegAdapter extends RoundPeg is
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        return Math.sqrt(Math.pow((peg.getWidth()/2), 2) * 2);


// Somewhere in client code.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(2)
large_sqpeg = new SquarePeg(5)
hole.fits(small_sqpeg) // won't compile (incompatible types)

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

適用性

當你想服用已經存在的類,但是接口和應用其他代碼不兼容

採用適配器模式創建一箇中間層來將調用轉換成應用中已存在對象可以處理的數據。

你需要複用幾個已經存在的類,但是她們缺少一些常用的功能。並且你無法在父類中添加這些功能,因爲他是閉源或者被其他代碼使用的。

你可以把這些缺少的功能放到新建的adapter中。他將連接你應用的代碼和你感興趣的類。這種解決方式看起來很像Visitor模式。

如何實現

  1. 確保你有兩種元素:

    • 有用的service對象
    • 應用代碼必須使用service對象。應用不能夠直接調用service,因爲接口或者數據格式不兼容。
  2. 聲明後面需要adpater跟隨的client接口。應用將使用這個接口和adapter交互。

  3. 創建一個adpater,實現client接口(空實現)。

  4. adapter增加一個service變量。通常情況下,這個變量在構造方法中設置。簡單的場景下,適配器可直接轉發調用(直接調用service中對應的方法)。

  5. 實現client定義的接口。adapter方法直接調用service中適當的方法並傳遞格式化後的數據。

  6. adapter類編寫完成後,在應用中通過client接口來使用它。

優缺點

  • 優點:隱藏了客戶端代碼不需要知道的接口實現細節和數據轉換

  • 缺點:引入新的類,使得整體複雜度增加

和其他模式的聯繫

  • Bridge(橋接)是先期設計把抽象和實現獨立。Adapter是進行改裝,使沒有關係的類在一起工作。Adapter是在設計後期使一些功能工作;Bridge在前期做這些事情。

  • Adapter爲了實現目標,提供不同的接口。Proxy(代理)提供相同的接口。Decorator(裝飾)提供增強的接口。

  • Adapter意味着改變現有代碼的接口。Decorator另一個對象的接口但是沒有改變接口。因而Decorator比Adapter更透明。結果就是,Decorator支持遞歸組合,純粹的適配器做不到這點。

  • Facade(門面)定義了一個新接口,而Adapter複用已有接口。記住,Adapter使兩個存在的接口協作而不是完全定義一個新接口。

  • Sate(狀態),Strategy(策略),Bridge(某種程度上的Adapter)有類似的解決結構。他們都是共享”handle/body”元素。他們的意圖不同,所以,他們解決不同的問題。

Java中模式的使用

用例:Adapter模式在Java代碼中很常見。它經常用於基於一些遺留代碼的系統中。在這種情況下,Adapter使得老代碼變得符合現在的需要。

在Java核心庫中有一些標準的Adapter:

  • java.util.Arrays#asList()

  • java.util.Collections#list()

  • java.util.Collections#enumeration()

  • java.io.InputStreamReader(InputStream) (returns a Reader object)

  • java.io.OutputStreamWriter(OutputStream) (returns a Writer object)

  • javax.xml.bind.annotation.adapters.XmlAdapter#marshal() and #unmarshal()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章