適配器模式 Adapter Design Pattern

definition

適配器模式,有時候也成爲包裝樣式或者包裝 (wrapper)。將一個類的接口轉接成用戶所期待的,使得因接口不兼容而不能在一塊兒工做的類能夠在一塊兒工做。 — wikipediahtml

適配器模式屬於結構型模式。這一類型的模式主要是爲了解決如何組織現有的類,設計他們的交互方式,從而達到必定的目的。包括了外觀模式、代理模式、裝飾模式、橋接模式、組合模式、享元模式以及今天要說的適配器模式。java

那麼問題來了,適配器模式究竟是爲了解決什麼問題呢?編程

problems to solve

在軟件開發領域,咱們常常面臨的一個問題就是你須要把一個方形的木頭楔進一個圓形的洞裏面!這個問題在現實生活中可能除了『削它』以外沒別的什麼好辦法,不過軟件世界中,由於是『軟的』,咱們能夠很是方便的進行各類改造來知足咱們的需求。數組

如上圖所示,經過在 Client 和 Service 兩個對象之間加入一個 Adapter 對象,兩個原本沒法在一塊兒工做的類如今能夠合做了。從這個圖也很容易看出來,adapter 的其實就是在 client 和 service 之間建立了一箇中間層,這又應驗了一句老話『計算機科學領域的任何問題均可以經過增長一個間接的中間層來解決』。app

Adapter 模式的概念很是容易理解,在現實世界中咱們也很容易找到相似概念的東西。dom

real world analogy (類比)

好比咱們在作演示的時候常常須要使用一個視頻數據線來鏈接筆記本和投影儀。從筆記本的角度看(做爲 client),它只能經過 usb-c 向外輸出視頻信號,但咱們的投影儀(做爲 service)的接口是固定的,只接受 HDMI 的信號,沒法更改(更改爲本比較高)。一個 usb-c to HDMI 的數據線就起到了 adapter 的做用。編程語言

相似的還有不少,好比手機的充電器,蘋果的雷電口轉 3.5mm 耳機口轉換器,好比咱們出國旅行都須要帶的插頭轉換器,三通閥門,等等。ide

structure

根據適配器的實現方式,能夠分對象適配器和類適配器兩個類型。ui

object adapter

對象適配器使用了對象組合的方式,adapter 實現一個類的接口,並內部容納了另一個類對象。spa

  • Client 是系統內部的現有代碼
  • Client Interface 描述了與 Client 代碼交互必須遵循的協議。
  • Service 包含了一些有用的類(一般是第三方庫或者遺留代碼),但 Client 不能直接使用,由於接口不兼容
  • Adapter 實現了 Client 的接口,內部包含了一個 Service 對象。Adapter 對象經過接口接受 Client 的調用,並將調用轉換爲 Service 對象可以理解的格式
  • client 側的代碼只依賴接口,與 adapter 代碼徹底解耦,所以你能夠建立多種多樣的 adapter 而不用擔憂對現有的 client/service 代碼產生任何的影響

class adapter

類適配器使用了多繼承的方式。 adapter 同時從兩個對象類繼承,因此這種方式一般在支持多繼承的編程語言出現,好比 C++。

在這種模式下,adapter 不須要 wrap service 對象了,由於它經過繼承,既是 client 又是 sercie,在 client 調用的方法中直接調用本身繼承的 service 方法就能夠了。

Java 由於不支持多繼承,一般沒法實現這種模式,但也能夠實現相似的形式:讓 adapter 來繼承 Service 類並實現 Client Interface。與多繼承的差異就是 adapter 仍是須要本身來實現 client 接口定義的方法。

which one to choose?

  1. 從實現上:對象適配器使用組合的方式,屬於動態組合;而類適配器使用繼承的方式,屬於靜態定義
  2. 從工做模式上:對象適配器容許一個 adapter 組合多個 service,並能夠兼容 service 的子類;而類適配器直接繼承了 service 類,所以不能對 service 的子類進行適配
  3. 從定義的角度:對象適配器很難重定義 service 的行爲,而類適配器由於使用了繼承的方式,能夠經過覆寫重定義 service 的部分行爲

整體來講,建議使用對象適配器的方式。

hands-on

咱們來找一個實際的例子來看看適配器到底應該怎麼用。

在 Java 標準庫中,Arrays.asList() 就實現了適配器模式。

/** * Returns a fixed-size list backed by the specified array. (Changes to * the returned list "write through" to the array.) This method acts * as bridge between array-based and collection-based APIs, in * combination with {@link Collection#toArray}. ... */
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}
複製代碼

這個方法接受變長參數的元素,返回了一個 ArrayList 對象,此 ArrayList 非彼 ArrayList,它是在 Arrays 類的一個 private 內部類,充當的是適配器的角色。

private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }

    @Override
    public int size() {
        return a.length;
    }
...
複製代碼

這個適配器類的會將 List 接口方法的調用代理給內部的數組對象。這個適配器類主要做用是爲了協調以數組爲基礎的 API 和以集合爲基礎的 API。在這個例子中,service 就是數組,client interface 就是 List,而依賴 List 的 API 就是 client。

pros & cons

  • 優點 適配器也是一種包裝模式,還有委託的意思在裏面,它適合在系統後期擴展、修改時候使用,由於只是新引入的 adapter 類,不須要對 client 和 service 作任何修改,很是的靈活。 適配器模式也符合 SOLID 中的單一職責原則開閉原則。前者主要體如今它將接口轉換的工做從 client 分離出來,由適配器來承擔;後者主要體如今適配器不須要 client 和 service 作任何的改動,而是經過繼承來實現須要的功能。

  • 劣勢 有得必有失。靈活性的增長一般也意味着複雜度的增長。 由於引入了新的類,應用的總體複雜度增長了。若是不加限制的過分使用適配器,會讓系統變的很是混亂,所以若是條件容許的話,也許更好的方案是直接對系統進行重構。

comparison / relations with others

  • 適配器是讓東西在設計實現以後可以適應新需求;橋接模式則是在他們設計的時候就想好了,可讓兩部分的開發獨立的進行
  • Adapter 提供了不一樣的接口(對於被 adapter 的對象,好比 usb-c 轉 hdmi),而 Proxy 則提供是一樣的接口,Decorator 提供的是加強的接口
  • Adapter 目的是爲了改變已經存在的某個對象的接口,Decorator 則是爲了避免改變原來接口的前提下提供加強的接口。另外 Decorator 支持嵌套的組合,而 Adapter 則不行
  • Facade 爲已經存在的對象增長了一個新的接口,而 Adapter 則是讓已有的接口變得可用。Adapter 一般只包含一個對象,而 Facade 則一般是定義了一個子系統全部對象的接口
  • Bridge, State, Strategy, Adapter 有很是像的結構,實際上,這些模式都是基於 composition,也就是說將工做 delegate 給其餘對象完成,可是這些模式解決的是不一樣的問題。一個模式並不只僅是代碼組合的特殊方式,它也能幫助其餘的開發者理解這個模式解決的問題。

references

Adapter | refactoring.guru

適配器模式原理及實例介紹 | ibm.com

適配器模式 | runoob.com

@monkeyM

相關文章
相關標籤/搜索