文章已收錄我的倉庫:Java學習筆記與免費書籍分享
設計意圖
適配器模式(Adapter Pattern)是作為兩個不兼容的接口之間的橋梁。這種類型的設計模式屬於結構型模式,它結合了兩個獨立接口的功能。
在某些時候,客戶期望獲得某種功能接口但現有的接口無法滿足客戶的需求,例如美國的正常供電電壓為110V,一個中國人帶了一款中國制造電器去美國,這個電器必須要在220V電壓下才能充電使用。這種情況下,客戶(中國人)的期望接口是有一個220V的電壓為電器充電
,但實際的接口是僅有一個110V的電壓供電器充電
,這種情況下就需要采用一根電壓轉換器(適配器)使得110V的電壓能夠轉換為220V的電壓,供客戶使用。
將一個類的接口轉換成客戶希望的另外一個接口,這就是適配器需要做的事情,適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。
適用條件
- 系統需要使用現有的類,而此類的接口不符合系統的需要(核心需求)。
- 想要建立一個可以重復使用的適配器類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作,這些源類不一定有一致的接口,但通過適配器使得它們都具有一致的接口。
- 通過接口轉換,將一個類插入另一個類系中。(比如老虎和飛禽,現在多了一個飛虎,在不增加實體的需求下,增加一個適配器,在里面包容一個虎對象,實現飛的接口。)
設計
通常有兩種方式實現適配器模式,一種是類適配器,類適配器目前已不太使用,另一種實現方式是對象適配器,通常情況下采用對象適配器會使得代碼更易擴展與維護。
不管采用何種方式,其基本的實現思想都是:對現有接口的實現類進行擴展,使其實現客戶期望的目標接口。
類適配器通過繼承現有接口類並實現目標接口,這樣的話會使得現有接口類完全對適配器暴露,使得適配器具有現有接口類的全部功能,破壞了封裝性。此外從邏輯上來說,這也是不符合常理的,適配器要做的是擴展現有接口類的功能而不是替代,類適配器只有在特定條件下會被使用。
對象適配器持有現有接口類一個實例,並擴展其功能,實現目標接口。這是推薦的方式,優先采用組合而不是繼承,會使得代碼更利於維護。此外,這也是非常符合常理的——“給我一根線,讓我來給他加長到5m,我並不需要知道這跟線是什么組成的,因為我的工作就是讓線加長到5m”——我們擴展了相應功能而並不關心其具體實現。
類適配器結構圖:
對象適配器結構圖:
- Target:客戶期望獲得的功能接口(220V電壓供電)。
- Cilent:客戶,期望訪問Target接口(客戶期望能有220V電壓)。
- Adaptee:現有接口,這個接口需要被適配(現有110V電壓供電,需要被適配至220V)。
- Adapter:適配器類,適配現有接口使其符合客戶需求接口(適配110V電壓,使其變為220V電壓)。
在適配器模式中,Cilent調用Adapter以獲得相應功能,Adapter擴展Adaptee以實現對應功能。
代碼示例
類適配器:
//客戶期望的接口——220V的電壓充電
interface Target {
void chargeBy220V();
}
//現有接口——只能通過110V電壓充電
interface Adaptee {
void chargeBy110V();
}
//現有接口的具體實現類,美國供電器——通過110V電壓供電
class americanCharger implements Adaptee {
@Override
public void chargeBy110V() {
System.out.println("美國供電器,只為你服務,正在通過110V電壓為您充電");
}
}
//類適配器,通過繼承現有接口來完成對現有接口的擴展
class Adapter extends americanCharger implements Target {
@Override
public void chargeBy220V() {
super.chargeBy110V();//現有功能
System.out.println("再加110V,達到220V,沖鴨!");//對現有功能擴展
}
}
//測試類
public class Test {
public static void main(String[] args) throws FileNotFoundException {
//類適配器使得代碼邏輯混亂
//這種情況下仿佛Adapter是一種110V的美國供電器可以直接使用不需要其他信息
//具體可以和對象適配器對比以下
new Adapter().chargeBy220V();
}
}
//輸出
/*
美國供電器,只為你服務,正在通過110V電壓為您充電
再加110V,達到220V,沖鴨!
*/
對象適配器:
//客戶期望的接口——220V的電壓充電
interface Target {
void chargeBy220V();
}
//現有接口——只能通過110V電壓充電
interface Adaptee {
void chargeBy110V();
}
//現有接口的具體實現類,美國供電器——通過110V電壓供電
class americanCharger implements Adaptee {
@Override
public void chargeBy110V() {
System.out.println("美國供電器,只為你服務,正在通過110V電壓為您充電");
}
}
//類適配器,通過繼承現有接口來完成對現有接口的擴展,使得能夠110V供電
class Adapter implements Target {
Adaptee adaptee;//持有現有接口具體實現對象的引用
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void chargeBy220V() {
adaptee.chargeBy110V();//該對象的現有功能
System.out.println("再加110V,達到220V,沖鴨!");//對現有功能擴展
}
}
//測試類
public class Test {
public static void main(String[] args) throws FileNotFoundException {
//現在我們有一個美國110V供電站,但我們無法使用
Adaptee adaptee = new americanCharger();
//我們將這個供電器交給適配器,適配器轉換為220V供電器
Adapter adapter = new Adapter(adaptee);
//接下來我們通過適配器充電就好了
adapter.chargeBy220V();
}
}
//輸出同上
對象適配器采用組合的方式實現對現有接口的擴展以達到客戶期望的接口。
讓我們來看JavaIO流中的一個實例:
FileInputStream fis = new FileInputStream("qe");
InputStreamReader isrAdapter = new InputStreamReader(fis);
BufferedReader bf = new BufferedReader(isrAdapter);
BufferedReader(此處為客戶)需要讀取文件字符流進行工作,讀取文件字符流就是客戶的需求部分,但是根據現有的接口,想要讀取文件就只能讀取字節流,FileInputStream就是現有接口的一個具體實現類,為了滿足客戶的需求,我們要對現有的接口進行適配,InputStreamReader就是一個適配器,它持有一個現有接口類的實例,通過這個實例讀取文件字節流並將其擴展為字符流以滿足客戶的需求,這是標准的對象適配器模式。如果仔細研究源碼,發現JavaIO庫將適配器定義為抽象的,並由具體的適配器繼承該抽象適配器,如這里的InputStreamReader就是具體的適配器之一。
如果實現適配有多種方式的話,我們可以將適配器類Adapter聲明為抽象類,並由子類擴展它:
//客戶期望的接口——220V的電壓充電
interface Target {
void chargeBy220V();
}
//現有接口——只能通過110V電壓充電
interface Adaptee {
void chargeBy110V();
}
//現有接口的具體實現類,美國供電器——通過110V電壓供電
class americanCharger implements Adaptee {
@Override
public void chargeBy110V() {
System.out.println("美國供電器,只為你服務,正在通過110V電壓為您充電");
}
}
//抽象類適配器,通過繼承現有接口來完成對現有接口的擴展
abstract class Adapter implements Target {
Adaptee adaptee;//持有現有接口具體實現對象的引用
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
}
//中國自制
class ChinaMakeAdapter extends Adapter {
public ChinaMakeAdapter(Adaptee adaptee) {
super(adaptee);
}
@Override
public void chargeBy220V() {
adaptee.chargeBy110V();//該對象的現有功能
System.out.println("再加110V,達到220V,認准中國制造,沖鴨!");//對現有功能擴展
}
}
//測試類
public class Test {
public static void main(String[] args) throws FileNotFoundException {
//現在我們有一個美國110V供電站,但我們無法使用
Adaptee adaptee = new americanCharger();
//我們將這個供電站交給中國制造的適配器
Adapter adapter = new ChinaMakeAdapter(adaptee);
//接下來我們通過適配器充電就好了
adapter.chargeBy220V();
}
}
//輸出同上
此外可以適配器還通過實現兩個接口以達到雙向適配的目的,即從接口A可以適配到接口B,從接口B也可以適配到接口A,這種情況並不常見。
//接口A——220V的電壓供電
interface A {
void chargeBy220V();
}
//接口A的具體實現類,中國供電器——通過220V電壓供電
class ChinaCharger implements A {
@Override
public void chargeBy220V() {
System.out.println("220V電壓中國充電,值得信賴");
}
}
//接口B——110V電壓供電
interface B {
void chargeBy110V();
}
//接口B的具體實現類,美國供電器——通過110V電壓供電
class AmericanCharger implements B {
@Override
public void chargeBy110V() {
System.out.println("美國充電器,只為你服務,正在通過110V電壓為您充電");
}
}
//雙向適配器
class Adapter implements A, B {
A a; //220V充電
B b; //110V充電
public Adapter(A a) {
this.a = a;
}
public Adapter(B b) {
this.b = b;
}
@Override
public void chargeBy220V() {
b.chargeBy110V(); //當前接口
System.out.println("加碼,加到220V!!");//適配目標接口
}
@Override
public void chargeBy110V() {
a.chargeBy220V();//當前接口
System.out.println("緩沖電壓,現在是110V了");
}
}
//測試類
public class Test {
public static void main(String[] args) throws FileNotFoundException {
//我們去美國,酒店里有一個美國110V充電站,我們需要220V的電壓
B b = new AmericanCharger();
//我們將這個充電站交給適配器以獲取220V電壓充電
Adapter adapter1 = new Adapter(b);
//接下來我們通過適配器充電就好了
adapter1.chargeBy220V();
System.out.println();
//美國人來中國,酒店里有一個中國220V充電站,但他需要110V的電壓
A a = new ChinaCharger();
//將這個充電站交給適配器以獲取110V電壓充電
Adapter adapter2 = new Adapter(a);
//接下來我們通過適配器充電就好了
adapter2.chargeBy110V();
}
}
//輸出
/*
美國充電器,只為你服務,正在通過110V電壓為您充電
加碼,加到220V!!
220V電壓中國充電,值得信賴
緩沖電壓,現在是110V了
Process finished with exit code 0
*/
通過實現兩個接口的方式,達到不同接口的雙向適配,在某些情況下還是很實用的,例如TypeC—USB接口轉換器,既能從typeC轉USB也能從USB轉typeC。
適配器模式總結
優點:
- 可以讓任何兩個沒有關聯的類一起運行。
- 提高了類的復用,可以一致化多個不同接口。
- 將現有接口實現類隱藏,增加了類的透明度。
- 靈活性高,可自由適配。
缺點:
- 過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
- 某些適配工作可能非常困難,例如讓房子飛起來。
當我們有動機地修改一個正常運行的系統的接口,這時應該考慮使用適配器模式。
注意事項:適配器不是在詳細設計時添加的,而是解決正在服役的項目的問題,即現有接口可能無法改變(去美國不可能把人家110V電壓供給改成220V電壓供給)。