Java設計模式之——適配器模式

適配器模式簡單介紹

適配器模式在我們的開發中使用率極高,從代碼中隨處可見的 Adapter 就可以判斷出來。從最早的 ListView、GridView到現在最新的 RecyclerView 都需要使用 Adapter,並且在開發中我們遇到的優化問題、出錯概率較大的地方也基本都出自 Adapter,這是個讓人又愛又恨的角色。
說到底,適配器是將兩個不兼容的類融合在一起,它有點向粘合劑,將不同的東西通過一種轉換使得它們能夠協作起來。例如,經常碰到要在兩個沒有關係的類型之間進行交互,第一個解決方案是修改各自類的接口,但是如果沒有源代碼或者我們不願意爲了一個應用而修改各自的接口,此時怎麼辦?這種情況我們往往會使用一個 Adapter,在這兩種接口之間建立一個“混血兒”接口,這個 Adapter 會將這兩個接口進行兼容,在不修改原有代碼的情況下滿足需求。

適配器模式的定義

適配器模式把一個類的即可變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。

適配器模式的使用場景

  • (1)系統需要使用現有的類,而此類的接口不符合系統的需要,即接口不兼容。
  • (2)想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
  • (3)需要一個統一的輸出接口,而輸入端的類型不可預知。

適配器模式的 UML 類圖

適配器模式分爲兩種,即類適配器模式和對象適配器模式,首先先來看類適配器模式,結構圖如下所示:

這裏寫圖片描述

如圖所示,類適配器是通過實現 Target 接口以及繼承 Adapter 類來實現接口轉換,例如,目標接口需要的是 operation2,但是 Adapter 對象只有一個 operation3,因此就出現了不兼容的情況,此時通過 Adapter 實現一個 operation2 函數將 Adapter 的 operation3 轉換爲 Target 需要的 operation2,以此實現兼容。

角色介紹:

  • Target:目標角色,也就是所期待得到的接口。注意:由於這裏討論的是類適配器模式,因此目標不可以是類。
  • Adaptee:現在需要適配的接口。
  • Adapter:適配器角色,也是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不可以是接口,而必須是具體類。

適配器模式實戰

用電源接口做例子,筆記本電腦的電源一般都是用 5V 電壓,但是我們生活中的電線電壓一般都是 220V。這個時候就出現了不匹配的狀況,在軟件開發中我們稱之爲接口不兼容,此時就需要適配器來進行一個接口轉換。在軟件開發中有一句話正好體現了這點:任何問題都可以加一箇中間層來解決。這個層我們可以理解爲這裏的 Adapter 層,通過這層來進行一個接口轉換就達到了兼容的目的。

在上述電源接口這個示例中,5V 電壓就是 Target 接口,220V 電壓就是 Adaptee 類,而將電壓從 220V 轉換爲 5V 就是 Adapter。

類適配器模式

具體程序如下所示:

/**
 * Traget角色
 */
public interface FiveVolt {
    int getVolt5();
}

/**
 * adaptee角色,需要被轉換的對象
 */
public class Volt220 {
    public int getVolt220() {
        return 220;
    }
}

/**
 * Adapter 角色,將 220V 的電壓轉換爲 5V 的電壓
 */
public class VoltAdapter extends Volt220 implements FiveVolt {
    @Override
    public int getVolt5() {
        return 5;
    }
}

Target 角色給出了需要的目標接口,而 Adaptee 類則是需要被轉換的對象。Adapter 則是將 Volt220 轉換成 Target 的接口。對應的 Target 的目標是要獲取 5V 的輸出電壓,而 Adaptee 正常輸出電壓是 220V,此時就需要電源適配器類將 220V 的電壓轉換爲 5V 電壓,解決接口不兼容的問題:

public class Test {
    public static void main() {
        VoltAdapter adapter = new VoltAdapter();
        Log.d("輸出電壓", "" + adapter.getVolt5());
    }
}

對象適配器模式

與類適配器模式一樣,對象的適配器模式把被適配的類的 API 轉換成爲目標類的 API,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關係連接到 Adaptee 類,而是使用代理關係連接到 Adaptee 類,UML 類圖如下所示:

這裏寫圖片描述

從圖中可以看出,Adaptee 類(Volt220)並沒有 getVolt5() 方法,而客戶端則期待這個方法。爲使客戶端能夠使用 Adaptee 類,需要提供一個包裝類 Adapter。這個包裝類包裝了 Adaptee 的實例,從而此包裝類能夠把 Adaptee 的 API 與 Target 類的 API 銜接起來。Adapter 與 Adaptee 是委派關係,這決定了適配器模式是對象的。示例代碼如下:

/**
 * Traget角色
 */
public interface FiveVolt {
    int getVolt5();
}

/**
 * adaptee角色,需要被轉換的對象
 */
public class Volt220 {
    public int getVolt220() {
        return 220;
    }
}

/**
 * Adapter 角色,將 220V 的電壓轉換爲 5V 的電壓
 */
public class VoltAdapter implements FiveVolt {
    Volt220 mVolt220;

    public VoltAdapter(Volt220 adaptee){
        this.mVolt220 = adaptee;
    }

    public int getVolt220() {
        return mVolt220.getVolt220();
    }

    @Override
    public int getVolt5() {
        return 5;
    }
}

使用示例代碼如下:

public class Test {
    public static void main() {
        VoltAdapter adapter = new VoltAdapter(new Volt220());
        Log.d("輸出電壓", "" + adapter.getVolt5());
    }
}

這種實現方式直接將要適配的對象傳遞到 Adapter 中,使用組合的形式實現接口兼容的效果。這比類適配器方式更爲靈活,它的另一個好處是被適配對象中的方法不會暴露出來,而類適配器由於繼承了被適配器對象,因此,被適配對象類的函數在 Adapter 類中也都含有,這使得 Adapter 類出現一些奇怪的接口,用戶使用成本較高。因此,對象適配器模式更加靈活、使用。

在實際開發中 Adapter 通常應用於進行不兼容的類型轉換的場景,還有一種就是輸入有無數中情況,但是輸出類型是統一的,我們可以通過 Adapter 返回一個統一的輸出,而具體的輸入留給用戶處理,內部只需要知道輸出的是符合要求的類型即可。例如 ListView 的 Adapter,用戶的 Item View 格式各樣,但最終都是屬於 View 類型,ListView 只需要知道 getView 返回的是一個 View 即可,具體是什麼 View 類型並不要 ListView 關心。而在使用 Adapter 模式的過程中建議儘量使用對象適配器的實現方式,多用合成或者聚合,少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合纔是最好的。

上面的兩個例子並不是很恰當,想要更多瞭解適配器的朋友可以看下這篇文章,在細細體會下:

Java設計模式透析之 —— 適配器(Adapter)

總結

優點:

  • 更好的複用性,系統需要使用現有的類,而此類的接口不符合系統的需要。那麼通過適配器模式就可以讓這些功能得到更好的複用。
  • 更好的擴展性,在實現適配器的時候,可以調用自己開發的功能,從而自然地擴展系統的功能。

缺點:

  • 過多地使用適配器,會讓系統非常凌亂,不宜整體把握。例如,明明開到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統如果太多出現這種情況,無異於一場災難。因此,如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章