本文通過老王使用紙質書籍閱讀小王使用電子書籍的故事,詳細說明設計模式中的結構型設計模式之配接器模式,分別對物件配接器和類配接器代碼實作,最后為了加深理解,會列舉配接器設計模式在JDK和Spring原始碼中的應用,
讀者可以拉取完整代碼到本地進行學習,實作代碼均測驗通過后上傳到碼云,
一、引出問題
自從小王被老王趕出家門以后,老王過了幾天舒心的日子,在家里的書架上買了許許多多的紙質書,
有一天,小王過夠了野人生活回來了,小王也是一個喜歡讀書的人,但是小王不喜歡紙質書,就要求老王將這些書換成電子版,
老王立馬就不開心了,這是我不知道花費多少個日夜才設計好的書架,給你換成電子版的不僅要花費我大量的精力改變原有書架的結構,再想找我想看的書得有多難,而且老李來了想看紙質版怎么辦,我還要再換回去嗎?
小王隨即想到了一種解決思路:這些書現在符合你的風格,應該設計一種模式,讓這些書也能符合我的需求,讓我們倆可以在一起讀書,既不改變你的書架結構,又能擴展它的功能,
老王滿意的點了點頭,你說的不錯,這實際上就是結構型設計模式中的配接器模式,
二、概念與使用
參考Gof中對配接器設計模式的概念:將一個類的介面轉化成客戶希望的另一個介面,由于介面不兼容而不能一起作業的類可以一起作業,
很顯然,在配接器設計模式中應該有三個角色,
目標類:Target,該角色把其他類轉換為我們期望的介面,可以是一個抽象類或介面,也可以是具體類,
被適配者類(源): Adaptee ,原有的介面,也是希望被適配的介面,
配接器: Adapter, 將被適配者和目標抽象類組合到一起的類,
在我們的實際案例中,老王的紙質書很明顯應該是屬于被適配者,小王的電子版就是目標類,配接器應該是能呼叫老王的紙質書,并使用一些相關的業務方法轉化成電子版,比如呼叫老王書之前買一個掃描儀,在老王書調出來以后掃描書籍,
既然配接器中要呼叫老王的紙質書,呼叫它的方法應該是有兩種實作方式,
一是直接繼承老王,那樣就可以直接呼叫老王的方法了,
二是在配接器中創建老王的物件,然后再呼叫老王的方法,
這其實對應了配接器的兩種方式,根據配接器類與適配者類的關系不同,配接器模式可分為物件配接器和類配接器兩種,在物件配接器模式中,配接器與適配者之間是關聯關系;在類配接器模式中,配接器與適配者之間是繼承(或實作)關系,
我們先看類配接器實作方式:
被適配者類:
/**
* 源物件
* @author tcy
* @Date 04-08-2022
*/
public class AdapteePaperReading {
public void readPaper(){
System.out.println("這是老王讀的紙質書...(被適配者方法)");
}
}
目標物件:
/**
* 目標物件
*/
public interface TargetOnlineReading {
public void ReadOnline();
}
配接器:
/**
* @author tcy
* @Date 04-08-2022
*/
public class Adapter extends AdapteePaperReading implements TargetOnlineReading{
@Override
public void ReadOnline() {
System.out.println("買一個掃描儀...");
readPaper();
System.out.println("拿到紙質書掃描為電子書...");
}
}
客戶端:
/**
* @author tcy
* @Date 04-08-2022
*/
public class Client {
public static void main(String[] args) {
Adapter adapter=new Adapter();
adapter.ReadOnline();
}
}
以上就實作類配接器,如果我們要實作物件配接器也很簡單,目標物件和被適配者都不變,需要改變的是配接器代碼
/**
* @author tcy
* @Date 04-08-2022
*/
public class Adapter implements TargetOnlineReading {
// 適配者是物件配接器的一個屬性
private AdapteePaperReading adaptee = new AdapteePaperReading();
@Override
public void ReadOnline() {
System.out.println("買一個掃描儀...");
adaptee.readPaper();
System.out.println("拿到紙質書掃描為電子書...");
}
}
這樣老王和小王就能在一起讀書了,但這種方式只能作為系統的一種補救措施,而不是在系統設計之初就考慮這種方式,如果老王有十個八個兒子都要求按照他們的習慣來,那系統就會相當的復雜,無異于一場災難,而是應該考慮重做書架,將各種情況都考慮進去,
需要說明的是,類配接器之間的耦合度比后者高,且要求程式員了解現有組件庫中的相關組件的內部結構,所以應用相對較少些,
三、應用
案例有一些生硬,為了加深對配接器設計模式的把握,我們介紹該模式在Jdk原始碼和Spring中的應用,
1、JDK應用
JDK使用配接器的典型例子是Java執行緒池FutureTask類,我們知道通過實作介面實作多執行緒一共有兩種方式,Runnable介面和Callable介面,
FutrueTask類中有兩個構造方法:
構造方法一:傳入引數為Callable介面
// 這是FutureTask的構造方法一
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
構造方法二:傳入的引數為Runnable介面
// 這是FutureTask的構造方法二
public FutureTask(Runnable runnable, V result) {
// 呼叫Executors類中的callable方法進行轉化
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
在構造方法中實際上加傳入的Runnable任務在內部統一被轉換為Callable任務,
可以看到這里采用的是配接器模式,呼叫RunnableAdapter<T>(task, result)方法來適配,實作如下:
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
這樣無論是傳入Runnalbe還是Callable都能適配任務,這個配接器很簡單,就是簡單的實作了Callable介面,在call()實作中呼叫Runnable.run()方法,然后把傳入的result作為任務的結果回傳,
通過這么一個簡單案例可以加深對配接器模式的理解,
2、SpringAOP應用
我們知道在Spring的Aop中,使用的 Advice(通知) 來增強被代理類的功能,
其中Advice的型別有:BeforeAdvice(在執行切點前的通知)、AfterReturningAdvice(在運行完切點完未回傳之前)、ThrowsAdvice(在運行完切點時拋出例外進行的通知),AfterAdvice(執行完該切點后,進行的通知)、Around advice(包裹一個方法的執行)
在每個型別 Advice 都有對應的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、 ThrowsAdviceInterceptor
Spring需要將每個 Advice 都封裝成對應的攔截器型別,回傳給容器,這時候采用的就是配接器型別,
Advice 就相當于適配者,對應的攔截器型別就是目標類,
限于篇幅,有興趣的讀者可以到Spring原始碼中了解具體程序,
3、SpringMVC應用
Spring MVC中的配接器模式主要用于執行目標 Controller 中的請求處理方法,
在Spring MVC中,DispatcherServlet 作為用戶,HandlerAdapter 作為期望介面,具體的配接器實作類用于對目標類進行適配,Controller 作為需要適配的類,
當Spring容器啟動后,會將所有定義好的配接器物件存放在一個List集合中,當一個請求來臨時,DispatcherServlet 會通過 handler 的型別找到對應配接器,并將該配接器物件回傳給用戶,然后就可以統一通過配接器的 hanle() 方法來呼叫 Controller 中的用于處理請求的方法,
通過配接器模式我們將所有的 controller 統一交給 HandlerAdapter 處理,免去了寫大量的 if-else 陳述句對 Controller 進行判斷,也更利于擴展新的 Controller 型別,
單純的說蒼白無力,我們手寫實作SpringMVC的核心流程,完整代碼已經上傳到碼云,
四、總結
既然配接器模式可以擴展原有類的功能,那它和代理模式在一定程度上不是重合了嗎?貌似擴展老王的書架使用代理模式同樣是可以實作,
其實我們看結構型設計模式的定義:結構型模式涉及到如何組合類和類以獲得更大的結構,結構型類模式采用繼承機制來組****合介面或實作,
代理模式與配接器模式都分別有繼承、介面方式實作的子分類模式,基于介面實作的代理模式稱為靜態代理模式、JDK(動態)代理模式,基于繼承實作的代理模式稱為Cglib(動態)代理模式,
基于介面(同時含類繼承)實作的配接器模式稱為類配接器模式,(只)基于繼承(使用委托)實作的配接器模式稱為類配接器模式,
代理模式是為其他類提供一種代理以控制對這個類的訪問,我們不直接去接觸目標類,而是直接操作代理類,代理類再去操作目標類,因為不直接接觸目標類,因此我們可以在代理類的同名方法中添加或洗掉功能模塊,而不用去修改目標類的原方法,
而配接器模式則主要是協調現實與需求的差異,減少對已有代碼的改動,適配不同的介面、型別別,
專案實施中可能會出現這樣的情況:當前已完成的專案的某一個包內的各個類實作了一些特定的介面,而客戶提出了新的需求,要求實作他所指定的那些介面(拋棄原有的方法或介面),但其業務細節卻是相同、完全一樣的,此時,我們可能并不想復制粘貼原代碼到新的方法中去,這就需要將一個類的介面轉換成新需求的另一個介面,
實作方式有很多,沒有必要咬文嚼字糾結使用哪種設計模式,設計模式本身就是很相似,只要能簡潔開發流程,讓我們的代碼更好的作業就是完美的,具體使用哪一種就需要讀者熟練掌握各種設計模式了,并認真體會他們各自的優勢,
推薦讀者,參考軟體設計七大原則 認真閱讀往期的文章,認真體會,
創建型設計模式:
一、設計模式之工廠方法和抽象工廠
二、設計模式之單例和原型
三、設計模式之建造者模式
結構型設計模式:
四、設計模式之代理模式
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/501015.html
標籤:設計模式
上一篇:面向物件的基礎知識
