Java設(shè)計模式之適配器模式
本文通過老王使用紙質(zhì)書籍閱讀小王使用電子書籍的故事,詳細(xì)說明設(shè)計模式中的結(jié)構(gòu)型設(shè)計模式之適配器模式,分別對對象適配器和類適配器代碼實現(xiàn),最后為了加深理解,會列舉適配器設(shè)計模式在JDK和Spring源碼中的應(yīng)用。
讀者可以拉取完整代碼到本地進(jìn)行學(xué)習(xí),實現(xiàn)代碼均測試通過后上傳到碼云,本地源碼下載。
一、引出問題
自從小王被老王趕出家門以后,老王過了幾天舒心的日子,在家里的書架上買了許許多多的紙質(zhì)書。
有一天,小王過夠了野人生活回來了,小王也是一個喜歡讀書的人,但是小王不喜歡紙質(zhì)書,就要求老王將這些書換成電子版。
老王立馬就不開心了,這是我不知道花費多少個日夜才設(shè)計好的書架,給你換成電子版的不僅要花費我大量的精力改變原有書架的結(jié)構(gòu),再想找我想看的書得有多難,而且老李來了想看紙質(zhì)版怎么辦,我還要再換回去嗎?
小王隨即想到了一種解決思路:這些書現(xiàn)在符合你的風(fēng)格,應(yīng)該設(shè)計一種模式,讓這些書也能符合我的需求,讓我們倆可以在一起讀書,既不改變你的書架結(jié)構(gòu),又能擴(kuò)展它的功能。
老王滿意的點了點頭,你說的不錯,這實際上就是結(jié)構(gòu)型設(shè)計模式中的適配器模式。
二、概念與使用
引用Gof中對適配器設(shè)計模式的概念:將一個類的接口轉(zhuǎn)化成客戶希望的另一個接口,由于接口不兼容而不能一起工作的類可以一起工作。
很顯然,在適配器設(shè)計模式中應(yīng)該有三個角色。
目標(biāo)類:Target,該角色把其他類轉(zhuǎn)換為我們期望的接口,可以是一個抽象類或接口,也可以是具體類。
被適配者類(源): Adaptee ,原有的接口,也是希望被適配的接口。
適配器: Adapter, 將被適配者和目標(biāo)抽象類組合到一起的類。
在我們的實際案例中,老王的紙質(zhì)書很明顯應(yīng)該是屬于被適配者,小王的電子版就是目標(biāo)類,適配器應(yīng)該是能調(diào)用老王的紙質(zhì)書,并使用一些相關(guān)的業(yè)務(wù)方法轉(zhuǎn)化成電子版,比如調(diào)用老王書之前買一個掃描儀,在老王書調(diào)出來以后掃描書籍。
既然適配器中要調(diào)用老王的紙質(zhì)書,調(diào)用它的方法應(yīng)該是有兩種實現(xiàn)方式。
一是直接繼承老王,那樣就可以直接調(diào)用老王的方法了。
二是在適配器中創(chuàng)建老王的對象,然后再調(diào)用老王的方法。
這其實對應(yīng)了適配器的兩種方式,根據(jù)適配器類與適配者類的關(guān)系不同,適配器模式可分為對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關(guān)聯(lián)關(guān)系;在類適配器模式中,適配器與適配者之間是繼承(或?qū)崿F(xiàn))關(guān)系。
我們先看類適配器實現(xiàn)方式:
被適配者類:
/** * 源對象 * @author tcy * @Date 04-08-2022 */ public class AdapteePaperReading { public void readPaper(){ System.out.println("這是老王讀的紙質(zhì)書...(被適配者方法)"); } }
目標(biāo)對象:
/** * 目標(biāo)對象 */ 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("拿到紙質(zhì)書掃描為電子書..."); } }
客戶端:
/** * @author tcy * @Date 04-08-2022 */ public class Client { public static void main(String[] args) { Adapter adapter=new Adapter(); adapter.ReadOnline(); } }
以上就實現(xiàn)類適配器,如果我們要實現(xiàn)對象適配器也很簡單,目標(biāo)對象和被適配者都不變,需要改變的是適配器代碼
/** * @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("拿到紙質(zhì)書掃描為電子書..."); } }
這樣老王和小王就能在一起讀書了。但這種方式只能作為系統(tǒng)的一種補(bǔ)救措施,而不是在系統(tǒng)設(shè)計之初就考慮這種方式,如果老王有十個八個兒子都要求按照他們的習(xí)慣來,那系統(tǒng)就會相當(dāng)?shù)膹?fù)雜,無異于一場災(zāi)難。而是應(yīng)該考慮重做書架,將各種情況都考慮進(jìn)去。
需要說明的是,類適配器之間的耦合度比后者高,且要求程序員了解現(xiàn)有組件庫中的相關(guān)組件的內(nèi)部結(jié)構(gòu),所以應(yīng)用相對較少些。
三、應(yīng)用
案例有一些生硬,為了加深對適配器設(shè)計模式的把握,我們介紹該模式在Jdk源碼和Spring中的應(yīng)用。
1、JDK應(yīng)用
JDK使用適配器的典型例子是Java線程池FutureTask類。我們知道通過實現(xiàn)接口實現(xiàn)多線程一共有兩種方式,Runnable接口和Callable接口。
FutrueTask類中有兩個構(gòu)造方法:
構(gòu)造方法一:傳入?yún)?shù)為Callable接口
// 這是FutureTask的構(gòu)造方法一 public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; }
構(gòu)造方法二:傳入的參數(shù)為Runnable接口
// 這是FutureTask的構(gòu)造方法二 public FutureTask(Runnable runnable, V result) { // 調(diào)用Executors類中的callable方法進(jìn)行轉(zhuǎn)化 this.callable = Executors.callable(runnable, result); this.state = NEW; }
在構(gòu)造方法中實際上加傳入的Runnable任務(wù)在內(nèi)部統(tǒng)一被轉(zhuǎn)換為Callable任務(wù)。
可以看到這里采用的是適配器模式,調(diào)用RunnableAdapter<T>(task, result)
方法來適配,實現(xiàn)如下:
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都能適配任務(wù),這個適配器很簡單,就是簡單的實現(xiàn)了Callable接口,在call()實現(xiàn)中調(diào)用Runnable.run()方法,然后把傳入的result作為任務(wù)的結(jié)果返回。
通過這么一個簡單案例可以加深對適配器模式的理解。
2、SpringAOP應(yīng)用
我們知道在Spring的Aop中,使用的 Advice(通知) 來增強(qiáng)被代理類的功能。
其中Advice的類型有:BeforeAdvice(在執(zhí)行切點前的通知)、AfterReturningAdvice(在運行完切點完未返回之前)、ThrowsAdvice(在運行完切點時拋出異常進(jìn)行的通知),AfterAdvice(執(zhí)行完該切點后,進(jìn)行的通知)、Around advice(包裹一個方法的執(zhí)行)
在每個類型 Advice 都有對應(yīng)的攔截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、 ThrowsAdviceInterceptor
Spring需要將每個 Advice 都封裝成對應(yīng)的攔截器類型,返回給容器,這時候采用的就是適配器類型。
Advice 就相當(dāng)于適配者,對應(yīng)的攔截器類型就是目標(biāo)類。
3、SpringMVC應(yīng)用
Spring MVC中的適配器模式主要用于執(zhí)行目標(biāo) Controller 中的請求處理方法。
在Spring MVC中,DispatcherServlet 作為用戶,HandlerAdapter 作為期望接口,具體的適配器實現(xiàn)類用于對目標(biāo)類進(jìn)行適配,Controller 作為需要適配的類。
當(dāng)Spring容器啟動后,會將所有定義好的適配器對象存放在一個List集合中,當(dāng)一個請求來臨時,DispatcherServlet 會通過 handler 的類型找到對應(yīng)適配器,并將該適配器對象返回給用戶,然后就可以統(tǒng)一通過適配器的 hanle() 方法來調(diào)用 Controller 中的用于處理請求的方法。
通過適配器模式我們將所有的 controller 統(tǒng)一交給 HandlerAdapter 處理,免去了寫大量的 if-else 語句對 Controller 進(jìn)行判斷,也更利于擴(kuò)展新的 Controller 類型。
單純的說蒼白無力,我們手寫實現(xiàn)SpringMVC的核心流程,完整代碼已經(jīng)上傳到碼云。
四、總結(jié)
既然適配器模式可以擴(kuò)展原有類的功能,那它和代理模式在一定程度上不是重合了嗎?貌似擴(kuò)展老王的書架使用代理模式同樣是可以實現(xiàn)。
其實我們看結(jié)構(gòu)型設(shè)計模式的定義:結(jié)構(gòu)型模式涉及到如何組合類和類以獲得更大的結(jié)構(gòu),結(jié)構(gòu)型類模式采用繼承機(jī)制來組合接口或?qū)崿F(xiàn)。
代理模式與適配器模式都分別有繼承、接口方式實現(xiàn)的子分類模式?;诮涌趯崿F(xiàn)的代理模式稱為靜態(tài)代理模式、JDK(動態(tài))代理模式,基于繼承實現(xiàn)的代理模式稱為Cglib(動態(tài))代理模式。
基于接口(同時含類繼承)實現(xiàn)的適配器模式稱為類適配器模式,(只)基于繼承(使用委托)實現(xiàn)的適配器模式稱為類適配器模式。
代理模式是為其他類提供一種代理以控制對這個類的訪問。我們不直接去接觸目標(biāo)類,而是直接操作代理類,代理類再去操作目標(biāo)類。因為不直接接觸目標(biāo)類,因此我們可以在代理類的同名方法中添加或刪除功能模塊,而不用去修改目標(biāo)類的原方法。
而適配器模式則主要是協(xié)調(diào)現(xiàn)實與需求的差異,減少對已有代碼的改動,適配不同的接口、類類型。
項目實施中可能會出現(xiàn)這樣的情況:當(dāng)前已完成的項目的某一個包內(nèi)的各個類實現(xiàn)了一些特定的接口,而客戶提出了新的需求,要求實現(xiàn)他所指定的那些接口(拋棄原有的方法或接口),但其業(yè)務(wù)細(xì)節(jié)卻是相同、完全一樣的。此時,我們可能并不想復(fù)制粘貼原代碼到新的方法中去,這就需要將一個類的接口轉(zhuǎn)換成新需求的另一個接口。
實現(xiàn)方式有很多,沒有必要咬文嚼字糾結(jié)使用哪種設(shè)計模式,設(shè)計模式本身就是很相似,只要能簡潔開發(fā)流程,讓我們的代碼更好的工作就是完美的。具體使用哪一種就需要讀者熟練掌握各種設(shè)計模式了,并認(rèn)真體會他們各自的優(yōu)勢。
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
mybatis的foreach標(biāo)簽語法報錯的解決
這篇文章主要介紹了mybatis的foreach標(biāo)簽語法報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02druid監(jiān)控?zé)o法關(guān)閉的坑以及處理方式
這篇文章主要介紹了druid監(jiān)控?zé)o法關(guān)閉的坑以及處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs
這篇文章主要介紹了詳解spring cloud整合Swagger2構(gòu)建RESTful服務(wù)的APIs,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01如何使用java.security.SecureRandom安全生成隨機(jī)數(shù)和隨機(jī)字符串工具類
這篇文章主要給大家介紹了關(guān)于如何使用java.security.SecureRandom安全生成隨機(jī)數(shù)和隨機(jī)字符串工具類的相關(guān)資料,SecureRandom擴(kuò)展了Random類,并通過在java 8中添加的新方法得到了豐富,需要的朋友可以參考下2024-05-05IntelliJ IDEA 15款超級牛逼插件推薦(自用,超級牛逼)
這篇文章主要給大家推薦介紹了IntelliJ IDEA 15款超級牛逼插件,這15款插件都是自用的,真的非常推薦,需要的朋友可以參考下2020-11-11