1. 程式人生 > >一天一個設計模式(五) - 適配器模式(Adapter)

一天一個設計模式(五) - 適配器模式(Adapter)

p s func 靈活性 nsh ans target 多線程 isp pattern

前言

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

適配器模式的用途

最經典的就是電器的例子,筆記本電腦的插頭一般都是三相的,即除了陽極、陰極之外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦無法使用。這時候一個三相到兩相的轉換器(適配器)就能解決此問題,而這正像是本模式所做的事情。

適配器模式的形式

適配器模式類的適配器模式對象的適配器模式兩種不同的形式。

正文

類的適配器模式

類的適配器模式,簡單來說,就是適配的類API轉換成為目標接口API

技術分享圖片

從上圖可以看出,Adaptee類並沒有sampleOperation2()方法,而客戶端則期待這個方法。

為了使客戶端能夠使用Adaptee類,提供一個中間環節,即類Adapter,把Adaptee類的APITarget接口的API銜接起來。AdapterAdaptee繼承關系,這決定了這個適配器模式是類的適配器模式

相關角色

  1. 目標(Target)角色:這就是所期待得到的接口。註意:由於這裏討論的是類適配器模式,因此目標不可以是類。
  2. 源(Adaptee)角色:現在需要適配的到目標角色的類。
  3. 適配器(Adapter)角色:適配器是目標角色源角色之間的橋梁。適配器把源角色的類轉換成目標接口
    的實現。

示例代碼

Target.java

1
2
3
4
5
6
7
8
9
10
11
public interface Target {
/**
* 這是源類Adaptee中也有的方法
*/

public void sampleOperation1();
/**
* 這是源類Adaptee中沒有的方法
*/
public void sampleOperation2();
}

上面給出的是目標角色的接口代碼,這個角色是以一個接口的形式實現的。可以看出,這個接口聲明了兩個方法:sampleOperation1()sampleOperation2()

,而源角色Adaptee是一個具體類,它有一個sampleOperation1()方法,但是沒有sampleOperation2()方法。

Adaptee.java

1
2
3
4
5
public class Adaptee {
public void sampleOperation1() {
System.out.println("Operation 1st");
}
}

適配器角色Adapter拓展了Adaptee,同時又實現了目標角色Target接口。由於Adaptee沒有提供sampleOperation2()方法,而目標接口有要求這個方法,因此適配器角色Adapter實現了這個方法。

Adapter.java

1
2
3
4
5
6
public class Adapter extends Adaptee implements Target {
@Override
public void sampleOperation2() {
System.out.println("Operation 2nd");
}
}

對象的適配器模式

類的適配器模式一樣,對象的適配器模式把被適配類API轉換成為目標類API

類的適配器模式不同的是,對象的適配器模式不是使用繼承關系鏈接到Adaptee類,而是使用委派關系連接到Adaptee類。

技術分享圖片

從上圖可以看出,Adaptee類並沒有sampleOperation2()方法,而客戶端則期待這個方法。

為使客戶端能夠使用Adaptee類,需要提供一個包裝WrapperAdapter。這個包裝類包括了一個Adaptee的實例,從而此包裝類能夠把AdapteeAPITarget類的API銜接起來。Adapter類與Adaptee類是委派關系,這決定了適配器模式對象的。

相關角色

  1. 目標(Target)角色:這就是所期待得到的接口。註意:由於這裏討論的是類適配器模式,因此目標不可以是類。
  2. 源(Adaptee)角色:現在需要適配的到目標角色的類。
  3. 適配器(Adapter)角色:適配器是目標角色源角色之間的橋梁。適配器把源角色的類包裝到目標接口的實現中。

示例代碼

Target.java

1
2
3
4
5
6
7
8
9
10
11
public interface Target {
/**
* 這是源類Adaptee中也有的方法
*/

public void sampleOperation1();
/**
* 這是源類Adaptee中沒有的方法
*/
public void sampleOperation2();
}

上面給出的是目標角色的接口代碼,這個角色是以一個接口的形式實現的。可以看出,這個接口聲明了兩個方法:sampleOperation1()sampleOperation2(),而源角色Adaptee是一個具體類,它有一個sampleOperation1()方法,但是沒有sampleOperation2()方法。

Adaptee.java

1
2
3
4
5
public class Adaptee {
public void sampleOperation1() {
System.out.println("Operation 1st");
}
}

對象的適配器模式中,適配器角色中持有一個對源角色的引用,並在需要適配的方法中使用源角色的方法實現。

Adapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Adapter {
private Adaptee adaptee;

public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}

/**
* 源類Adaptee有方法sampleOperation1
* 因此適配器可以直接進行委派
*/
public void sampleOperation1() {
this.adaptee.sampleOperation1();
}

/**
* 源類Adaptee沒有方法sampleOperation2
* 因此適配器需要自己實現此方法
*/
public void sampleOperation2() {
System.out.println("Operation 2nd");
}
}

兩種適配器模式的對比

類的適配器模式

  1. 使用對象繼承的方式,是靜態的定義方式。
  2. 由於適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作。因為繼承是靜態的關系,而適配器繼承了Adaptee後,就不可能再去處理Adaptee子類了。
  3. 適配器可以重定義Adaptee的部分行為,相當於子類覆蓋父類的部分實現方法。
  4. 不需要額外的引用過來間接得到Adaptee

對象的適配器模式

  1. 使用對象組合的方式,是動態的組合方式。
  2. 一個適配器可以把多種不同的適配源適配到同一個目標類上。換言之,同一個適配器可以把源類它的子類都適配到目標接口。因為對象適配器采用的是對象組合的關系,只要對象類型正確,是不是子類都無所謂。
  3. 要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實現重定義,然後讓適配器組合子類。這樣,雖然增加了一定的復雜性,也提供了一定的靈活性。
  4. 需要額外的引用來間接得到Adaptee

建議盡量使用對象適配器的實現方式,多用合成/聚合,少用繼承。當然,具體問題還是需要具體分析,根據需要來選用實現方式,最適合的才是最好的。

總結

適配器模式的優點

  • 更好的復用性

系統需要使用現有的類,因此類的接口不符合系統的需要。那麽通過適配器模式就可以讓這些功能得到更好的復用。

  • 更好的拓展性

在實現適配器功能的時候,可以調用自己開發的功能,從而自然的拓展系統的功能。

適配器模式的缺點

過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A接口,其實內部都被適配成了B接口的實現。一個系統如果太多的出現這種情況,無異於異常災難。

因此如果不是很有必要,可以不是用適配器,而是直接對系統進行重構。


歡迎關註技術公眾號: 零壹技術棧

技術分享圖片零壹技術棧

本帳號將持續分享後端技術幹貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務,架構學習和進階等學習資料和文章。

一天一個設計模式(五) - 適配器模式(Adapter)