適配器模式:如何讓不兼容的接口變得兼容
在軟件開發中,我們經常會遇到這樣的情況:我們需要使用一個現有的類或者接口,但它與我們系統的目標接口不兼容,而我們又不能修改它。這時候,我們該怎么辦呢?大多數情況下我們都可以使用適配器模式來解決這個問題,本文將從以下四個方面講解適配器模式。
- 簡介
- 優缺點
- 應用場景
- Java 代碼示例
簡介
適配器模式(Adapter Pattern)是一種結構型設計模式,它可以將一個接口轉換成客戶端所期待的另一個接口,從而使原本由于接口不兼容而不能一起工作的類可以一起工作。適配器模式也稱為包裝器模式(Wrapper Pattern),因為它通過一個包裝類(即適配器)來包裝不兼容的接口,并提供統一的目標接口。適配器模式可以在運行時根據需要選擇不同的適配器來適配不同的被適配者。
對象適配器模式的各角色定義如下。
- Target(目標接口):客戶端要使用的目標接口標準,對應下文中的三相插孔接口 TriplePin。
- Adapter(適配器):實現了目標接口,負責適配(轉換)被適配者的接口 specificRequest()為目標接口 request(),對應本章下文中的電視機專屬適配器類 TriplePinAdapter。
- Adaptee(被適配者):被適配者的接口標準,目前不能兼容目標接口的問題接口,可以有多種實現類,對應下文中的兩相插孔接口 DualPin。
- Client(客戶端):目標接口的使用者。
優缺點
適配器模式的優點有:
- 適配器模式可以增強程序的可擴展性,通過使用適配器,可以在不修改原有代碼的基礎上引入新的功能或者接口。
- 適配器模式可以提高類的復用性,通過使用適配器,可以將已有的類或者接口重新組合和封裝,使其符合新的需求。
- 適配器模式可以增加類的透明度,通過使用適配器,客戶端只需要關注目標接口,而無需了解被適配者的具體實現。
- 適配器模式可以靈活地切換不同的被適配者,通過使用不同的適配器,可以動態地選擇不同的被適配者來滿足不同的場景。
適配器模式的缺點有:
- 適配器模式會增加系統的復雜性,過多地使用適配器會使系統變得零亂和難以理解。
- 適配器模式可能會降低系統的性能,因為每次調用目標接口時都需要經過適配器的轉換。
- 適配器模式可能會違反開閉原則,如果目標接口發生變化,則需要修改所有的適配器類。
應用場景
適配器模式適用于以下場景:
- 當需要在一個已有系統中引入新的功能或者接口時,它與系統的目標接口不兼容,但又不能修改原有代碼時,可以使用適配器模式。例如在一個數據庫操作系統中,如果想要支持多種類型的數據庫源,但系統只提供了一個固定類型數據庫源的操作接口時,可以使用一個數據庫源操作適配器來將不同類型數據庫源轉換成統一類型數據庫源。
- 當需要在多個獨立開發的系統或者組件之間進行協作時,但由于各自采用了不同的接口或者協議時,可以使用適配器模式。例如在一個分布式服務系統中,如果想要讓不同語言編寫的服務之間進行通信和調用,但各自采用了不同的通信協議和數據格式時,可以使用一個服務通信適配器來將不同協議和數據格式轉換成統一協議和數據格式。
Java 代碼示例
舉一個生活中常見的實例,我們新買了一臺電視機,其電源插頭是兩相的,不巧的是墻上的插孔卻是三相的,這時電視機便無法通電使用,我們以代碼來重現這個場景。
- 定義目標接口:三相插口 TriplePin,其中 3 個參數 l、n、e 分別對應火線(live)、零線(null)和地線(earth)。
public interface TriplePin {
public void electrify(int l, int n, int e);
}
- 定義被適配者接口:兩項插口 DualPin,可以看到參數中缺少了地線 e 參數。
public interface DualPin {
public void electrify(int l, int n);
}
- 添加被適配者接口具體實現類:TV,可以看到 TV 實現的是兩相接口,所在無法直接在三項接口中使用。
public class TV implements DualPin {
@Override
public void electrify(int l, int n) {
System.out.println("火線通電:" + l + ",零線通電:" + n);
System.out.println("電視開機");
}
}
- 定義適配器類:三項接口適配器 TriplePinAdapter,實現了三項接口并且包含兩項接口屬性,在 electrify 方法中調用被適配設備的兩插通電方法,忽略地線參數 e,以此來完成三項接口對兩項接口的兼容。
這也就意味著 TriplePinAdapter 類能幫助我們將 TV 類與三項接口兼容。
public class TriplePinAdapter implements TriplePin {
private DualPin dualPin;
public TriplePinAdapter(DualPin dualPin) {
this.dualPin = dualPin;
}
@Override
public void electrify(int l, int n, int e) {
// 調用被適配設備的兩插通電方法,忽略地線參數e
dualPin.electrify(l, n);
}
}
- 定義客戶端類
public class Client {
public static void main(String[] args) {
DualPin dualPinDevice = new TV();
TriplePin triplePinDevice = new TriplePinAdapter(dualPinDevice);
triplePinDevice.electrify(1, 0, -1);
}
}
輸出結果如下:
火線通電:1,零線通電:0
電視開機
總結
通過利用適配器模式對系統進行擴展后,我們就不必再為解決兼容性問題去暴力修改類接口了,轉而通過適配器,以更為優雅、巧妙的方式將兩側“對立”的接口“整合”在一起,順利化解雙方難以調和的矛盾,最終使它們順利接通。