結構性設計模式(四)介面卡模式【如何把方的變成圓的】
強扭的瓜,湊合著用
目錄
一、概念及實現
1.1 概念
介面卡模式的英文翻譯是 Adapter Design Pattern。顧名思義,這個模式就是用來做適配的,它將不相容的介面轉換為可相容的介面,讓原本由於介面不相容而不能一起工作的類可以一起工作。把兩種不相容的介面,通過轉接變得可以一起工作。
介面卡模式有兩種實現方式:類介面卡和物件介面卡。其中,類介面卡使用繼承關係來實現,物件介面卡使用組合關係來實現。
具體的程式碼實現如下所示。其中,ITarget 表示要轉化成的介面定義。Adaptee 是一組不相容 ITarget 介面定義的介面,Adaptor 將 Adaptee 轉化成一組符合 ITarget 介面定義的介面。
1.2 基於繼承的實現和基於組合的實現
- 基於繼承的實現
// 類介面卡: 基於繼承
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor extends Adaptee implements ITarget {
public void f1() {
super.fa();
}
public void f2() {
//...重新實現f2()...
}
// 這裡fc()不需要實現,直接繼承自Adaptee,這是跟物件介面卡最大的不同點
}
- 基於組合的實現
// 物件介面卡:基於組合
public interface ITarget {
void f1();
void f2();
void fc();
}
public class Adaptee {
public void fa() { //... }
public void fb() { //... }
public void fc() { //... }
}
public class Adaptor implements ITarget {
private Adaptee adaptee;
public Adaptor(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void f1() {
adaptee.fa(); //委託給Adaptee
}
public void f2() {
//...重新實現f2()...
}
public void fc() {
adaptee.fc();
}
}
1.3 使用哪一種
判斷的標準主要有兩個,一個是 Adaptee 介面的個數,另一個是 Adaptee 和 ITarget 的契合程度。
- 如果 Adaptee 介面並不多,那兩種實現方式都可以。
- 如果 Adaptee 介面很多,而且 Adaptee 和 ITarget 介面定義大部分都相同,那我們推薦使用類介面卡,因為 Adaptor 複用父類 Adaptee 的介面,比起物件介面卡的實現方式,Adaptor 的程式碼量要少一些。
- 如果 Adaptee 介面很多,而且 Adaptee 和 ITarget 介面定義大部分都不相同,那我們推薦使用物件介面卡,因為組合結構相對於繼承更加靈活。
二、適用的場景
介面卡模式可以看作一種“補償模式”,用來補救設計上的缺陷。應用這種模式算是“無奈之舉”。如果在設計初期,我們就能協調規避介面不相容的問題,那這種模式就沒有應用的機會了。
2.1 封裝有缺陷的介面設計
假設我們依賴的外部系統在介面設計方面有缺陷(比如包含大量靜態方法),引入之後會影響到我們自身程式碼的可測試性。為了隔離設計上的缺陷,我們希望對外部系統提供的介面進行二次封裝,抽象出更好的介面設計,這個時候就可以使用介面卡模式了。
舉個例子來解釋一下,你直接看程式碼應該會更清晰。具體程式碼如下所示:
public class CD { //這個類來自外部sdk,我們無權修改它的程式碼
//...
public static void staticFunction1() { //... }
public void uglyNamingFunction2() { //... }
public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }
public void lowPerformanceFunction4() { //... }
}
// 使用介面卡模式進行重構
public class ITarget {
void function1();
void function2();
void fucntion3(ParamsWrapperDefinition paramsWrapper);
void function4();
//...
}
// 注意:介面卡類的命名不一定非得末尾帶Adaptor
public class CDAdaptor extends CD implements ITarget {
//...
public void function1() {
super.staticFunction1();
}
public void function2() {
super.uglyNamingFucntion2();
}
public void function3(ParamsWrapperDefinition paramsWrapper) {
super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
}
public void function4() {
//...reimplement it...
}
}
2.2 統一多個類的介面設計
某個功能的實現依賴多個外部系統(或者說類)。通過介面卡模式,將它們的介面適配為統一的介面定義,然後我們就可以使用多型的特性來複用程式碼邏輯。具體我還是舉個例子來解釋一下。
假設我們的系統要對使用者輸入的文字內容做敏感詞過濾,為了提高過濾的召回率,我們引入了多款第三方敏感詞過濾系統,依次對使用者輸入的內容進行過濾,過濾掉儘可能多的敏感詞。但是,每個系統提供的過濾介面都是不同的。這就意味著我們沒法複用一套邏輯來呼叫各個系統。這個時候,我們就可以使用介面卡模式,將所有系統的介面適配為統一的介面定義,這樣我們可以複用呼叫敏感詞過濾的程式碼。你可以配合著下面的程式碼示例,來理解我剛才舉的這個例
public class ASensitiveWordsFilter { // A敏感詞過濾系統提供的介面
//text是原始文字,函式輸出用***替換敏感詞之後的文字
public String filterSexyWords(String text) {
// ...
}
public String filterPoliticalWords(String text) {
// ...
}
}
public class BSensitiveWordsFilter { // B敏感詞過濾系統提供的介面
public String filter(String text) {
//...
}
}
public class CSensitiveWordsFilter { // C敏感詞過濾系統提供的介面
public String filter(String text, String mask) {
//...
}
}
// 未使用介面卡模式之前的程式碼:程式碼的可測試性、擴充套件性不好
public class RiskManagement {
private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
public String filterSensitiveWords(String text) {
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterPoliticalWords(maskedText);
maskedText = bFilter.filter(maskedText);
maskedText = cFilter.filter(maskedText, "***");
return maskedText;
}
}
// 使用介面卡模式進行改造
public interface ISensitiveWordsFilter { // 統一介面定義
String filter(String text);
}
public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
private ASensitiveWordsFilter aFilter;
public String filter(String text) {
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterPoliticalWords(maskedText);
return maskedText;
}
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...
// 擴充套件性更好,更加符合開閉原則,如果新增一個新的敏感詞過濾系統,
// 這個類完全不需要改動;而且基於介面而非實現程式設計,程式碼的可測試性更好。
public class RiskManagement {
private List<ISensitiveWordsFilter> filters = new ArrayList<>();
public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
filters.add(filter);
}
public String filterSensitiveWords(String text) {
String maskedText = text;
for (ISensitiveWordsFilter filter : filters) {
maskedText = filter.filter(maskedText);
}
return maskedText;
}
}
2.3 替換依賴的外部系統
當我們把專案中依賴的一個外部系統替換為另一個外部系統的時候,利用介面卡模式,可以減少對程式碼的改動。具體的程式碼示例如下所示:
// 外部系統A
public interface IA {
//...
void fa();
}
public class A implements IA {
//...
public void fa() { //... }
}
// 在我們的專案中,外部系統A的使用示例
public class Demo {
private IA a;
public Demo(IA a) {
this.a = a;
}
//...
}
Demo d = new Demo(new A());
// 將外部系統A替換成外部系統B
public class BAdaptor implemnts IA {
private B b;
public BAdaptor(B b) {
this.b= b;
}
public void fa() {
//...
b.fb();
}
}
// 藉助BAdaptor,Demo的程式碼中,呼叫IA介面的地方都無需改動,
// 只需要將BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));
2.4 相容老版本介面
在做版本升級的時候,對於一些要廢棄的介面,我們不直接將其刪除,而是暫時保留,並且標註為 deprecated,並將內部實現邏輯委託為新的介面實現。這樣做的好處是,讓使用它的專案有個過渡期,而不是強制進行程式碼修改。這也可以粗略地看作介面卡模式的一個應用場景。同樣,我還是通過一個例子,來進一步解釋一下。
JDK1.0 中包含一個遍歷集合容器的類 Enumeration。JDK2.0 對這個類進行了重構,將它改名為 Iterator 類,並且對它的程式碼實現做了優化。但是考慮到如果將 Enumeration 直接從 JDK2.0 中刪除,那使用 JDK1.0 的專案如果切換到 JDK2.0,程式碼就會編譯不通過。為了避免這種情況的發生,我們必須把專案中所有使用到 Enumeration 的地方,都修改為使用 Iterator 才行。
單獨一個專案做 Enumeration 到 Iterator 的替換,勉強還能接受。但是,使用 Java 開發的專案太多了,一次 JDK 的升級,導致所有的專案不做程式碼修改就會編譯報錯,這顯然是不合理的。這就是我們經常所說的不相容升級。為了做到相容使用低版本 JDK 的老程式碼,我們可以暫時保留 Enumeration 類,並將其實現替換為直接呼叫 Itertor。程式碼示例如下所示:
public class Collections {
public static Emueration emumeration(final Collection c) {
return new Enumeration() {
Iterator i = c.iterator();
public boolean hasMoreElments() {
return i.hashNext();
}
public Object nextElement() {
return i.next():
}
}
}
}
三、主要參考內容
相關文章
- 設計模式(十)----結構型模式之介面卡模式設計模式
- 初探Java設計模式2:結構型模式(代理模式,介面卡模式等)Java設計模式
- Go 實現常用設計模式(四)介面卡模式Go設計模式
- 設計模式詳解之結構型設計模式——介面卡、裝飾器設計模式
- 結構型模式:介面卡模式模式
- 設計模式:介面卡模式設計模式
- 設計模式-介面卡模式設計模式
- 設計模式----介面卡模式設計模式
- 【設計模式】介面卡模式設計模式
- 設計模式系列之介面卡模式(Adapter Pattern)——不相容結構的協調設計模式APT
- 設計模式--介面卡模式/代理模式設計模式
- python 設計模式-介面卡模式Python設計模式
- 設計模式之介面卡模式設計模式
- JavaScript 設計模式 —— 介面卡模式JavaScript設計模式
- java設計模式-介面卡模式Java設計模式
- 設計模式(七)介面卡模式設計模式
- 設計模式(五):介面卡模式設計模式
- 設計模式之【介面卡模式】設計模式
- Java設計模式之介面卡設計模式Java設計模式
- Java進階篇設計模式之四 -----介面卡模式和橋接模式Java設計模式橋接
- 工作中常用的設計模式--介面卡模式設計模式
- 設計者模式之介面卡模式模式
- PHP設計模式-Adapter 介面卡模式PHP設計模式APT
- python設計模式之介面卡模式Python設計模式
- PHP設計模式(2)—— 介面卡模式PHP設計模式
- Java 設計模式(一)《介面卡模式》Java設計模式
- Javascript 設計模式之介面卡模式JavaScript設計模式
- PHP 設計模式之介面卡模式PHP設計模式
- Java設計模式之介面卡模式Java設計模式
- 設計模式【6.1】-- 初探介面卡模式設計模式
- 極簡設計模式-介面卡模式設計模式
- Java設計模式(6)----------介面卡模式Java設計模式
- 【Java】設計模式--結構型模式Java設計模式
- 介面卡設計模式設計模式
- Swift 中的設計模式 #3 外觀模式與介面卡模式Swift設計模式
- 設計模式 | Catalog設計模式,抵禦業務方需求變動設計模式
- 設計模式 #4 (裝飾器模式、介面卡模式)設計模式
- 設計模式第五講-介面卡模式設計模式