Java設(shè)計(jì)模式之適配器模式詳解
一、定義
適配器模式(Adapter Pattern):結(jié)構(gòu)型模式之一,將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶(hù)希望的另一個(gè)接口。
Adapter模式使得原本由于接口不兼容而不能一起工作的哪些類(lèi)可以一起工作。
二、UML類(lèi)圖

三、角色職責(zé)
- 目標(biāo)角色(Target):該角色定義把其他類(lèi)轉(zhuǎn)換為何種接口,也就是我們的期望接口。
- 源角色(Adaptee):你想把誰(shuí)轉(zhuǎn)換成目標(biāo)角色,這個(gè)“誰(shuí)”就是源角色,它是已經(jīng)存在的、運(yùn)行良好的類(lèi)或?qū)ο蟆?/li>
- 適配器角色(Adapter):適配器模式的核心角色,其他兩個(gè)角色都是已經(jīng)存在的角色,而適配器角色是需要新建立的,它的職責(zé)非常簡(jiǎn)單:通過(guò)繼承或是類(lèi)關(guān)聯(lián)的方式把源角色轉(zhuǎn)換為目標(biāo)角色。
四、代碼實(shí)現(xiàn)
前言:舉個(gè)栗子,我今天買(mǎi)了機(jī)票,飛到香港迪士尼去游玩,晚上回到了酒店,想給我的筆記本電腦充電,但這時(shí)我發(fā)現(xiàn),香港的插座是英式三角插座,我的充電器插不進(jìn)去。這時(shí)我們就可以使用適配器模式,進(jìn)行適配。
- 類(lèi)適配器:適配器通過(guò)類(lèi)來(lái)實(shí)現(xiàn),以類(lèi)來(lái)繼承和實(shí)現(xiàn)接口的方式,來(lái)獲取被適配類(lèi)的信息并轉(zhuǎn)換輸出重寫(xiě)到適配接口。
中式插座(源角色 Adaptee)
@AllArgsConstructor
@Data
public class ChineseStandard {
public String getChineseStandard() {
return "中式插座";
}
}英式插座(目標(biāo)角色 Target)
public interface BritishStandard {
String getBritishStandard();
}插座適配器(適配器角色 Adapter)
public class StandardAdapter extends ChineseStandard implements BritishStandard {
@Override
public String getBritishStandard() {
return this.getChineseStandard();
}
}筆記本電腦(客戶(hù)端 Client)
public class Notebook {
public void charge(BritishStandard britishStandard) {
if ("中式插座".equals(britishStandard.getBritishStandard())) {
System.out.println("充電成功!");
} else {
System.out.println("充電失敗!");
}
}
}測(cè)試類(lèi)
public class AdapterTest {
public static void main(String[] args) {
// 充電成功!
new Notebook().charge(new StandardAdapter());
}
}- 對(duì)象適配器:通過(guò)實(shí)例對(duì)象(構(gòu)造器傳遞)來(lái)實(shí)現(xiàn)適配器,而不是再用繼承,其余基本同類(lèi)適配器。
我么們將插座適配器就行修改即可
@AllArgsConstructor
public class StandardAdapter implements BritishStandard {
private ChineseStandard chineseStandard;
@Override
public String getBritishStandard() {
return chineseStandard.getChineseStandard();
}
}測(cè)試類(lèi)
public class AdapterTest {
public static void main(String[] args) {
// 充電成功!
new Notebook().charge(new StandardAdapter(new ChineseStandard()));
}
}如果我們的源目標(biāo)接口中還有一些其他我們不需要的方法,我們并不想去實(shí)現(xiàn)它,我們就可以將適配器作為一個(gè)抽象類(lèi),當(dāng)我們實(shí)現(xiàn)適配器抽象類(lèi)的時(shí)候只要重寫(xiě)我們需要的方法即可。這時(shí)候我們就用到了接口適配器。
- 接口適配器:當(dāng)不需要全部實(shí)現(xiàn)接口提供的方法時(shí),可先設(shè)計(jì)一個(gè)抽象類(lèi)實(shí)現(xiàn)接口,并為該接口中每個(gè)方法提供一個(gè)默認(rèn)實(shí)現(xiàn)(空方法),那么該抽象類(lèi)的子類(lèi)可有選擇地覆蓋父類(lèi)的某些方法來(lái)實(shí)現(xiàn)需求。
英式插座(目標(biāo)角色 Target)
public interface BritishStandard {
String getBritishStandard();
String getTypeC();
String getUSB();
}插座適配器(適配器角色 Adapter)
@AllArgsConstructor
public abstract class StandardAdapter extends ChineseStandard implements BritishStandard {
@Override
public String getBritishStandard() {
return null;
}
@Override
public String getTypeC() {
return null;
}
@Override
public String getUSB() {
return null;
}
}測(cè)試類(lèi)
public class AdapterTest {
public static void main(String[] args) {
StandardAdapter standardAdapter= new StandardAdapter() {
@Override
public String getBritishStandard() {
return new ChineseStandard().getChineseStandard();
}
};
// 充電成功!
new Notebook().charge(standardAdapter);
}
}五、源碼分析
我們先來(lái)看一下Spring MVC的工作原理

- 瀏覽器發(fā)送請(qǐng)求到 控制器(DispatcherServlet)
- 控制器 根據(jù)請(qǐng)求地址, 到 HandlerMapping(處理器映射) 尋找對(duì)應(yīng)的 Handler(處理器)
- HanldlerMapping 返回 找到的Handler
- DispatcherServlet 根據(jù)找到的Handler 找對(duì)應(yīng)的HandlerAdaptor
- 執(zhí)行對(duì)應(yīng)的Handler方法
- Handler 將執(zhí)行結(jié)果 和 要響應(yīng)的視圖名 封裝成 ModelAndView 對(duì)象
- 控制器根據(jù)返回的 ViewName 找對(duì)應(yīng)的ViewResolver (視圖解析ViewResolver 將 Model 渲染到 View 中
- 將渲染結(jié)果 返回給控制器
- 最終將結(jié)果響應(yīng)給客戶(hù)端瀏覽器
可以看出Spring MVC中的適配主要執(zhí)行Controller的請(qǐng)求處理方法。在Spring MVC中,DispatcherServlet作為用戶(hù),HandlerAdapter作為期望接口(目標(biāo)角色 Target),Controller則為源角色(Adaptee)。Spring MVC中的Controller種類(lèi)眾多,不同類(lèi)型的Controller通過(guò)不同的方法來(lái)對(duì)請(qǐng)求進(jìn)行處理。 我們首先看一下HandlerAdapter接口

Spring MVC提供的Controler如下。

Spring MVC提供的Adapter如下。

該接口的每一個(gè)Controller都有一個(gè)適配器與之對(duì)應(yīng),這樣的話,每自定義一個(gè)Controller需要定義一個(gè)實(shí)現(xiàn)HandlerAdapter的適配器。 我們進(jìn)入DispatcherServlet類(lèi),查看是如何獲得適配器的。


當(dāng)Spring容器啟動(dòng)后,會(huì)將所有定義好的適配器對(duì)象存放在一個(gè)List集合中,當(dāng)一個(gè)請(qǐng)求來(lái)臨時(shí),DispatcherServlet會(huì)通過(guò) handler的類(lèi)型找到對(duì)應(yīng)適配器,并將該適配器對(duì)象返回給用戶(hù),然后就可以統(tǒng)一通過(guò)適配器的handle()方法來(lái)調(diào)用Controller中的用于處理請(qǐng)求的方法。通過(guò)適配器模式我們將所有的Controller統(tǒng)一交給 HandlerAdapter 處理,免去了寫(xiě)大量的 if-else 語(yǔ)句對(duì) Controller進(jìn)行判斷,也更利于擴(kuò)展新的Controller類(lèi)型。
六、優(yōu)缺點(diǎn)分析
類(lèi)適配器 優(yōu)點(diǎn):可以根據(jù)需求重寫(xiě)Adaptee類(lèi)的方法,使得Adapter的靈活性增強(qiáng)了。 缺點(diǎn):有一定局限性。因?yàn)轭?lèi)適配器需要繼承Target類(lèi),而Java是單繼承機(jī)制,所以要求Adaptee類(lèi)必須是接口。
對(duì)象適配器 優(yōu)點(diǎn):同一個(gè)Adapter可以把Adaptee類(lèi)和他的子類(lèi)都適配到目標(biāo)接口。 缺點(diǎn):需要重新定義Adaptee行為時(shí),需要重新定義Adaptee的子類(lèi),并將適配器組合適配。
接口適配器 優(yōu)點(diǎn):可以靈活方便的選擇性重寫(xiě)接口方法。 缺點(diǎn):由于是匿名內(nèi)部類(lèi)的形式,所以不利于代碼復(fù)用。
七、適用場(chǎng)景
- 系統(tǒng)需要復(fù)用現(xiàn)有類(lèi),而該類(lèi)的接口不符合系統(tǒng)的需求,可以使用適配器模式使得原本由于接口不兼容而不能一起工作的那些類(lèi)可以一起工作。
- 多個(gè)組件功能類(lèi)似,但接口不統(tǒng)一且可能會(huì)經(jīng)常切換時(shí),可使用適配器模式,使得客戶(hù)端可以以統(tǒng)一的接口使用它們。
八、總結(jié)
適配器模式將現(xiàn)有接口轉(zhuǎn)化為客戶(hù)類(lèi)所期望的接口,實(shí)現(xiàn)了對(duì)現(xiàn)有類(lèi)的復(fù)用,它是一種使用頻率非常高的設(shè)計(jì)模式,在軟件開(kāi)發(fā)中得以廣泛應(yīng)用,Spring等開(kāi)源框架、驅(qū)動(dòng)程序設(shè)計(jì)(如JDBC中的數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序)中也都使用了適配器模式。
到此這篇關(guān)于Java設(shè)計(jì)模式之適配器模式詳解的文章就介紹到這了,更多相關(guān)Java適配器模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java語(yǔ)言中的自定義類(lèi)加載器實(shí)例解析
這篇文章主要介紹了Java語(yǔ)言中的自定義類(lèi)加載器實(shí)例解析,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
使用ThreadPoolExecutor之高效處理并發(fā)任務(wù)
這篇文章主要介紹了使用ThreadPoolExecutor之高效處理并發(fā)任務(wù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
Eureka源碼閱讀之環(huán)境搭建及工程結(jié)構(gòu)
這篇文章主要為大家介紹了Eureka源碼閱讀之環(huán)境搭建的工程結(jié)構(gòu)及調(diào)試需知詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2022-10-10
詳解 Hive UDF 開(kāi)發(fā)之Java 實(shí)現(xiàn)步驟與代碼調(diào)試技巧
這篇文章主要介紹了詳解 Hive UDF 開(kāi)發(fā)之Java實(shí)現(xiàn)步驟與代碼調(diào)試技巧,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-12-12
Spring AOP手動(dòng)實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)代理的代碼
今天小編就為大家分享一篇關(guān)于Spring AOP手動(dòng)實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)代理的代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
Java發(fā)送報(bào)文與接收?qǐng)?bào)文的實(shí)例代碼
這篇文章主要介紹了Java發(fā)送報(bào)文與接收?qǐng)?bào)文,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
springboot項(xiàng)目Redis統(tǒng)計(jì)在線用戶(hù)的實(shí)現(xiàn)示例
最近做個(gè)項(xiàng)目需要統(tǒng)計(jì)在線用戶(hù),本文主要介紹了springboot項(xiàng)目Redis統(tǒng)計(jì)在線用戶(hù)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
java階乘計(jì)算獲得結(jié)果末尾0的個(gè)數(shù)代碼實(shí)現(xiàn)
今天偶然看到一個(gè)要求,求1000~10000之間的數(shù)n的階乘并計(jì)算所得的數(shù)n!末尾有多少個(gè)0?要求: 不計(jì)算 只要得到末尾有多少個(gè)0就可以了,看下面的代碼吧2013-12-12

