介面卡模式-通過介面卡來複用
介面卡模式是一種結構型設計模式,介面卡模式主要是來解決介面不相容的問題,使得原本沒有關係的類可以協同工作。就好像我們膝上型電腦的電源介面卡,在電壓110v-220v之間都是可以正常工作的(那麼大一坨),這介面卡的工作就是把外部不穩定的電壓轉化成膝上型電腦能穩定使用的直流電壓
介面卡模式
定義
介面卡模式(Adapter Pattern)- 將一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作
使用介面卡模式其實就是把一個介面或者類轉換成其他的介面和類,使其可以和其他模組一起工作,也可以被稱為包裝模式(Wrapper),同樣有包裝的功能還有裝飾模式。介面卡模式主要應用於想要複用現有的類,但是介面又與複用環境不一致的情況下
經常換手機的人可能會知道,手機的充電介面有幾種,安卓的介面現在大多是Type-C
了,一些低端機可能還在用Micro USB
介面,而蘋果則是用Lightining
介面,假如我現在買了個新手機是Type-C
介面,那我以前的那個Micro USB
資料線還能不能繼續用呢,是可以用的,只不過需要加上一個轉接頭,這個轉接頭便是一個介面卡
介面卡模式結構
介面卡模式角色
- 目標類(Target):定義客戶所需介面,可以是一個抽象類或介面,也可以是具體類
- 適配者(Adaptee):需要被適配的角色,它是已經存在的類或物件,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的原始碼
- 介面卡(Adapter):它的職責就是要把適配者轉換成目標角色,對Adaptee和Target進行適配,在物件介面卡中,它通過繼承Target並關聯一個Adaptee物件使二者產生聯絡
介面卡有兩種分類:物件介面卡、類(或介面)介面卡。在物件介面卡模式中,介面卡與適配者之間是關聯關係;在類介面卡模式中,介面卡與適配者之間是繼承(或實現)關係
物件介面卡
類介面卡
優點和使用場景
優點
- 可以讓兩個沒有關係的類在一起執行,引入一個介面卡,可以不用修改原有程式碼
- 增加類的透明度,客戶端只呼叫Target,不關心具體實現
- 提高類的複用度,原適配者類的功能還可以正常使用
- 靈活性和擴充套件性非常好,符合“開閉原則”
使用場景
- 系統需要使用一些現有的類,而這些類的介面(如方法名)不符合系統的需要,甚至沒有這些類的原始碼
- 修改已投產的介面,可以考慮介面卡模式
注意,介面卡模式通常是用來解決系統擴充套件的問題,在系統開發過程中或已經專案維護中,當需要引入第三方的功能或要擴充套件的內容不符合原有設計的時候,才會考慮通過介面卡模式來減少程式碼修改帶來的風險。另外,專案一定要遵守依賴倒置原則和里氏替換原則,這樣在使用介面卡模式時,才不會有太多改動
總的來說,介面卡模式屬於補償模式,專門用來在系統後期擴充套件、修改時使用,但要注意不要過度使用介面卡模式
例項
前些天剛入手一臺新手機,滿心歡喜啊,拿到手就忍不住開始“摩擦“,掂量一圈發現現在的手機是越來越好看、越來越薄了呀,一開心就想著拿出我的耳機想要沉浸在音樂的世界裡,可下一秒蒙圈了,泥馬沒有耳機孔。原來啊,為了把手機做的更薄、更美,手機廠家犧牲了傳統的3.mm耳機插孔,而使用複用充電介面Type-C,這下難道我又要去再搞一條Type-C介面的耳機嗎?答案是可以不用,可以買一個介面轉換器
Target目標類 - 要求使用Type-C介面
public interface TypeC {
void useTypeCPort();
}
複製程式碼
Adaptee適配者 - 被適配的物件
public interface Headset {
void listen();
}
public class CommonHeadset implements Headset {
@Override
public void listen() {
System.out.println("使用3.5mm耳機享受音樂...");
}
}
複製程式碼
Adapter介面卡 - 把適配物件和目標類關聯起來,達到轉換的目的
public class CommonHeadsetAdapter implements TypeC {
private CommonHeadset headset;
public CommonHeadsetAdapter(CommonHeadset headset){
this.headset = headset;
}
@Override
public void useTypeCPort() {
System.out.println("使用Type-C轉接頭");
this.headset.listen();
}
}
複製程式碼
測試
@Test
public void adapterTest(){
CommonHeadset headset = new CommonHeadset();
CommonHeadsetAdapter headsetAdapter = new CommonHeadsetAdapter(headset);
headsetAdapter.useTypeCPort();
}
複製程式碼
測試結果
使用Type-C轉接頭
使用3.5mm耳機享受音樂...
複製程式碼
上面的是物件介面卡,下面的是類介面卡的做法
public class CommonHeadsetAdapter2 extends CommonHeadset implements TypeC {
@Override
public void useTypeCPort() {
System.out.println("使用Type-C轉接頭");
super.listen();
}
}
複製程式碼
介面卡模式應用
在我看來介面卡模式主要是用在系統的擴充套件,或接入第三方介面。我們都不想破壞原有的設計和結構,為了適應新的需求,在它們之間增加一個適配層,這個介面卡角色為我們轉換資料或做連線。這樣新的介面就能流暢呼叫舊的介面,達到複用的目的,符合開閉原則
要想理解介面卡模式的應用或自己使用介面卡,只要理清角色分工就很容易了。需要擴充套件一個怎樣的新需求 (目標角色),但不改變原有的設計結構 (被適配角色),這個被適配角色可以是現有的介面、物件、類或第三方API,然後使用一箇中間角色 **(介面卡)**做資料轉換或功能呼叫,使其滿足新需求
JDBC - 驅動程式 - 資料庫引擎API
Java程式是通過JDBC來跟資料庫連線的,JDBC給出了一套抽象的介面,即目標介面。而各種資料庫就是我們要適配的物件的,在JDBC介面和資料庫引擎API之間需要一個介面卡,而這個介面卡就是驅動程式。(比如MySql的連執行緒式就是一個介面卡,它把資料庫的API操作適配成JDBC的Java操作)
Java I/O 庫使用了介面卡模式
如ByteArrayInputStream
是一個介面卡類,它繼承了InputStream
的介面 (目標介面),並且封裝了一個 byte 陣列 (被適配者)。它將一個 byte 陣列的介面適配成InputStream
流處理器的介面;
FileOutputStream
繼承了 OutputStream
型別,同時持有一個對 FileDiscriptor
物件的引用。這是一個將 FileDiscriptor
介面適配成 OutputStream
介面形式的物件介面卡模式;
Reader
型別的原始流處理器都是介面卡模式的應用。StringReader
是一個介面卡類,StringReader
類繼承了Reader
型別,持有一個對String
物件的引用。它將String
的介面適配成 Reader
型別的介面
Spring AOP中的介面卡模式
在Spring的Aop中,使用Advice(通知)來增強被代理類的功能。
Advice **(被適配者)**的型別有:MethodBeforeAdvice
、AfterReturningAdvice
、ThrowsAdvice
,而每個型別的Advice都有對應的攔截器:MethodBeforeAdviceInterceptor
、AfterReturningAdviceInterceptor
、ThrowsAdviceInterceptor
。Spring需要將每個Advice都封裝成對應的攔截器型別,返回給容器,所以需要使用介面卡模式對Advice進行轉換
在這個應用中,Advice增強類其實只是普通的物件(被適配角色),而我們的目標是通過攔截器對切入點增強功能,所以通過介面卡將我們的Advice適配成攔截器達到增強切入點的目的
Adaptee適配者類
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method,Object[] args,@Nullable Object target) throws Throwable;
}
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(@Nullable Object returnValue,Method method,@Nullable Object target) throws Throwable;
}
public interface ThrowsAdvice extends AfterAdvice {
}
複製程式碼
Target目標介面
public interface AdvisorAdapter {
// 判斷是否支援該增強,即是否匹配
boolean supportsAdvice(Advice advice);
// 獲取對應的攔截器
MethodInterceptor getInterceptor(Advisor advisor);
}
複製程式碼
Adapter介面卡
class MethodBeforeAdviceAdapter implements AdvisorAdapter,Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
class AfterReturningAdviceAdapter implements AdvisorAdapter,Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof AfterReturningAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
return new AfterReturningAdviceInterceptor(advice);
}
}
class ThrowsAdviceAdapter implements AdvisorAdapter,Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof ThrowsAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
return new ThrowsAdviceInterceptor(advisor.getAdvice());
}
}
複製程式碼