介面不相容時,Adapter Pattern 能轉接介面,使不相容的類別能一起工作。
Adapter Pattern 用來「彌補」設計上的缺陷,以避免改動原有設計,實屬無奈之舉。
Adapter Pattern 實作
有二種實作方式,一是利用繼承的「類別轉接器」,二是利用組合的「物件轉接器」
類別轉接器
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 ITraget {
public void f1() {
super.fa();
}
public void f2() {
super.fb();
}
// fc 繼承 Adaptee,不需要實作
}
類別轉接器的優點是,可以繼承被轉接者,無需重新實作介面每個方法
物件轉接器
public interface ITarget {
/* same */
}
public class Adaptee {
/* same */
}
public class Adaptor implements ITraget {
private Adaptee adaptee;
public Adaptor(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void f1() {
adaptee.fa();
}
public void f2() {
adaptee.fb();
}
public void fc() {
adaptee.fc();
}
}
物件轉接器的優點是,組合更有彈性,轉接器也能轉接 Adaptee 的子類別
該選哪一種?
- Adaptee 與 ITarget 介面類似,選類別轉接器能提高複用性,減少程式碼量
- Adaptee 與 ITarget 介面大部分不同,選擇較有彈性的物件轉接器
Adapter Pattern 應用
1. 封裝有缺陷的介面
系統必須引入有許多問題的外部 SDK,例如靜態方法造成可測試性差、命名糟糕、過多參數、效能不足等,但又無法修改其程式碼
public class SDK {
public static void staticFunction1() { /*...*/ }
public static void uglyNamingFunction2() { /*...*/ }
public static void tooManyParamsFunction3(int a, int b, ...) { /*...*/ }
public static void lowPerformanceFunction4() { /*...*/ }
}
使用 Adapter Pattern 重構
public ITarget {
void function1();
void function2();
void function3(ParamsWrapper paramWrapper);
void function4();
}
public SDKAdaptor extends SDK implements Itarget {
public void function1() {
super.staticFunction1();
}
public void function2() {
super.uglyNamingFunction2();
}
public void function3(ParamsWrapper paramWrapper) {
super.tooManyParamsFunction3(paramWrapper.getParamA(), ...)
}
public void function4() {
// reimplement
}
}
2. 統一多個類別的介面
某個功能依賴多個外部系統的類別,將他們的介面統一,可以利用多型提高程式碼重複使用性
// 有多個過濾內容中敏感文字的類別
public class ASesitiveWordsFilter {
public String filterSexyWords(String text) { /*...*/ }
public String filterCurseWords(String text) { /*...*/ }
}
public class BSesitiveWordsFilter {
public String filter(String text) { /*...*/ }
}
public class CSesitiveWordsFilter {
public String filter(String text, String mask) { /*...*/ }
}
client 擴展性差、可測試性低
public class RiskManagement {
private ASesitiveWordsFilter aFilter = new ASesitiveWordsFilter();
private BSesitiveWordsFilter bFilter = new BSesitiveWordsFilter();
private CSesitiveWordsFilter cFilter = new CSesitiveWordsFilter();
public String filterSensitiveWord(String text) {
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterCurseWords(maskedText);
maskedText = bFilter.filter(maskedText);
maskedText = cFilter.filter(maskedText, "***");
return maskedText
}
}
使用 Adapter Pattern 重構
public interface ISensitiveWordsFilter {
String filter(String text);
}
public ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
private ASesitiveWordsFilter filter;
public ASensitiveWordsFilterAdaptor(ASesitiveWordsFilter filter) {
this.filter = filter;
}
public String filter(String text) {
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterCurseWords(maskedText);
return maskedText;
}
}
// 省略 BSesitiveWordsFilterAdaptor, CSesitiveWordsFilterAdaptor
public class RiskManagement {
private List<ISensitiveWordsFilter> filters = new ArrayList<>();
public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
filters.add(filter);
}
public String filterSensitiveWord(String text) {
String maskedText = text;
for (ISensitiveWordsFilter filter : filters) {
maskedText = filter.filter(maskedText);
}
return maskedText;
}
}
3. 替換依賴
原本依賴外部系統 A,要改成依賴外部系統 B,利用 Adapter Pattern 能減少改動
// 外部系統 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 demo = new Demo(new A());
利用 BAdaptor,Demo 類別依賴 IA 介面的地方無須修改
// 將系統 A 替換成系統 B
public class BAdaptor implemnts IA {
private B b;
public BAdaptor(B b) {
this.b= b;
}
public void fa() {
b.fb();
}
}
Demo demo = new Demo(new BAdaptor(new B()));
4. 相容舊版介面
在升級版本時,有些要棄用的介面,可以有個過渡期,暫時保留舊版本介面
JDK 1.0 有個集合容器類別 Enumeration,在 JDK 2.0 進行了重構,改名為 Iteraotr;升級後為了相容舊版 JDK 開發的程式,暫時保留了 Enumeration 類別
public class Collections {
public static Enmueration enumeration(final Collection c) {
return new Enumeration() {
Iterator i = c.iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public Object nextElement() {
return i.next();
}
}
}
}
實際應用
Slf4j,是一個 Java 的日誌框架,針對不同日誌框架進行封裝,提供統一介面;系統內只需要針對 Slf4j 介面來開發使用日誌的程式碼,具體使用哪種或多種日誌框架(log4j、logback...) 只要將對應的 SDK 導入專案,而無須修改原本程式