前言
介面卡模式把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。
介面卡模式的用途
最經典的就是電器的例子,膝上型電腦的插頭一般都是三相的,即除了陽極、陰極之外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與膝上型電腦的電源插頭不匹配使得膝上型電腦無法使用。這時候一個三相到兩相的轉換器(介面卡)就能解決此問題,而這正像是本模式所做的事情。
介面卡模式的形式
介面卡模式有類的介面卡模式和物件的介面卡模式兩種不同的形式。
正文
類的介面卡模式
類的介面卡模式,簡單來說,就是適配的類的API
轉換成為目標介面的API
。
從上圖可以看出,Adaptee
類並沒有sampleOperation2()
方法,而客戶端則期待這個方法。
為了使客戶端能夠使用Adaptee
類,提供一個中間環節,即類Adapter
,把Adaptee
類的API
同Target
介面的API
銜接起來。Adapter
與Adaptee
是繼承關係,這決定了這個介面卡模式是類的介面卡模式。
相關角色
- 目標(Target)角色:這就是所期待得到的介面。注意:由於這裡討論的是類介面卡模式,因此目標不可以是類。
- 源(Adaptee)角色:現在需要適配的到目標角色的類。
- 介面卡(Adapter)角色:介面卡是目標角色和源角色之間的橋樑。介面卡把源角色的類轉換成目標介面的實現。
示例程式碼
Target.java
public interface Target {
/**
* 這是源類Adaptee中也有的方法
*/
public void sampleOperation1();
/**
* 這是源類Adaptee中沒有的方法
*/
public void sampleOperation2();
}
複製程式碼
上面給出的是目標角色的介面程式碼,這個角色是以一個介面的形式實現的。可以看出,這個介面宣告瞭兩個方法:sampleOperation1()
和sampleOperation2()
,而源角色Adaptee
是一個具體類,它有一個sampleOperation1()
方法,但是沒有sampleOperation2()
方法。
Adaptee.java
public class Adaptee {
public void sampleOperation1() {
System.out.println("Operation 1st");
}
}
複製程式碼
介面卡角色Adapter
擴充了Adaptee
,同時又實現了目標角色Target
介面。由於Adaptee
沒有提供sampleOperation2()
方法,而目標介面有要求這個方法,因此介面卡角色Adapter
實現了這個方法。
Adapter.java
public class Adapter extends Adaptee implements Target {
@Override
public void sampleOperation2() {
System.out.println("Operation 2nd");
}
}
複製程式碼
物件的介面卡模式
與類的介面卡模式一樣,物件的介面卡模式把被適配類的API
轉換成為目標類的API
。
與類的介面卡模式不同的是,物件的介面卡模式不是使用繼承關係連結到Adaptee
類,而是使用委派關係連線到Adaptee
類。
從上圖可以看出,Adaptee
類並沒有sampleOperation2()
方法,而客戶端則期待這個方法。
為使客戶端能夠使用Adaptee
類,需要提供一個包裝Wrapper
類Adapter
。這個包裝類包括了一個Adaptee
的例項,從而此包裝類能夠把Adaptee
的API
與Target
類的API
銜接起來。Adapter
類與Adaptee
類是委派關係,這決定了介面卡模式是物件的。
相關角色
- 目標(Target)角色:這就是所期待得到的介面。注意:由於這裡討論的是類介面卡模式,因此目標不可以是類。
- 源(Adaptee)角色:現在需要適配的到目標角色的類。
- 介面卡(Adapter)角色:介面卡是目標角色和源角色之間的橋樑。介面卡把源角色的類包裝到目標介面的實現中。
示例程式碼
Target.java
public interface Target {
/**
* 這是源類Adaptee中也有的方法
*/
public void sampleOperation1();
/**
* 這是源類Adaptee中沒有的方法
*/
public void sampleOperation2();
}
複製程式碼
上面給出的是目標角色的介面程式碼,這個角色是以一個介面的形式實現的。可以看出,這個介面宣告瞭兩個方法:sampleOperation1()
和sampleOperation2()
,而源角色Adaptee
是一個具體類,它有一個sampleOperation1()
方法,但是沒有sampleOperation2()
方法。
Adaptee.java
public class Adaptee {
public void sampleOperation1() {
System.out.println("Operation 1st");
}
}
複製程式碼
在物件的介面卡模式中,介面卡角色中持有一個對源角色的引用,並在需要適配的方法中使用源角色的方法實現。
Adapter.java
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");
}
}
複製程式碼
兩種介面卡模式的對比
類的介面卡模式
- 使用物件繼承的方式,是靜態的定義方式。
- 由於介面卡直接繼承了
Adaptee
,使得介面卡不能和Adaptee
的子類一起工作。因為繼承是靜態的關係,而介面卡繼承了Adaptee
後,就不可能再去處理Adaptee
的子類了。 - 介面卡可以重定義
Adaptee
的部分行為,相當於子類覆蓋父類的部分實現方法。 - 不需要額外的引用過來間接得到
Adaptee
。
物件的介面卡模式
- 使用物件組合的方式,是動態的組合方式。
- 一個介面卡可以把多種不同的適配源適配到同一個目標類上。換言之,同一個介面卡可以把源類和它的子類都適配到目標介面。因為物件介面卡採用的是物件組合的關係,只要物件型別正確,是不是子類都無所謂。
- 要重定義
Adaptee
的行為比較困難,這種情況下,需要定義Adaptee
的子類來實現重定義,然後讓介面卡組合子類。這樣,雖然增加了一定的複雜性,也提供了一定的靈活性。 - 需要額外的引用來間接得到
Adaptee
。
建議儘量使用物件介面卡的實現方式,多用合成/聚合,少用繼承。當然,具體問題還是需要具體分析,根據需要來選用實現方式,最適合的才是最好的。
總結
介面卡模式的優點
- 更好的複用性
系統需要使用現有的類,因此類的介面不符合系統的需要。那麼通過介面卡模式就可以讓這些功能得到更好的複用。
- 更好的擴充性
在實現介面卡功能的時候,可以呼叫自己開發的功能,從而自然的擴充系統的功能。
介面卡模式的缺點
過多的使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是A
介面,其實內部都被適配成了B
介面的實現。一個系統如果太多的出現這種情況,無異於異常災難。
因此如果不是很有必要,可以不是用介面卡,而是直接對系統進行重構。
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。