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),這樣的話(huà),每自定義一個(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)文章
idea使用war以及war exploded的區(qū)別說(shuō)明
本文詳細(xì)解析了war與warexploded兩種部署方式的差異及步驟,war方式是先打包成war包,再部署到服務(wù)器上;warexploded方式是直接把文件夾、class文件等移到Tomcat上部署,支持熱部署,開(kāi)發(fā)時(shí)常用,文章分別列出了warexploded模式和war包形式的具體操作步驟2024-10-10java代碼實(shí)現(xiàn)mysql分表操作(用戶(hù)行為記錄)
這篇文章主要介紹了java代碼實(shí)現(xiàn)mysql分表操作(用戶(hù)行為記錄),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Springboot的自動(dòng)配置是什么及注意事項(xiàng)
SpringBoot的自動(dòng)配置(Auto-configuration)是指框架根據(jù)項(xiàng)目的依賴(lài)和應(yīng)用程序的環(huán)境自動(dòng)配置Spring應(yīng)用上下文中的Bean和組件,目的是簡(jiǎn)化開(kāi)發(fā)者的配置工作,本文介紹Springboot的自動(dòng)配置是什么及注意事項(xiàng),感興趣的朋友一起看看吧2025-03-03Spring Boot中Elasticsearch的連接配置原理與使用詳解
在Spring Boot中,我們可以通過(guò)Elasticsearch實(shí)現(xiàn)對(duì)數(shù)據(jù)的搜索和分析,本文將介紹Spring Boot中Elasticsearch的連接配置、原理和使用方法,感興趣的可以了解一下2023-09-09Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證
這篇文章主要介紹了Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05feign name指定服務(wù)調(diào)用無(wú)效問(wèn)題及解決
文章主要介紹了FeignClient注解的常用屬性,并通過(guò)一個(gè)具體的例子說(shuō)明了為什么某個(gè)Feign調(diào)用需要使用url指定路徑才能訪問(wèn),最后,文章給出了解決辦法,即使用path屬性指定前綴2024-11-11SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例
本篇文章主要介紹了SpringMVC 使用JSR-303進(jìn)行校驗(yàn) @Valid示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02Java實(shí)現(xiàn)創(chuàng)建運(yùn)行時(shí)類(lèi)的對(duì)象操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)創(chuàng)建運(yùn)行時(shí)類(lèi)的對(duì)象操作,結(jié)合實(shí)例形式分析了Java動(dòng)態(tài)創(chuàng)建對(duì)象的原理與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-08-08