介紹
生活中的案例
不同國家的插座不同,出國旅游充電器不能直接使用,可以通過使用多功能轉(zhuǎn)換插頭
來輔助使用
基礎(chǔ)介紹
-
適配器模式將某個(gè)類的接口轉(zhuǎn)換成客戶端期望的另一個(gè)接口表示,主的目的是兼容性,讓原本因接口不匹配不能一起工作的兩個(gè)類可以協(xié)同工作。其別名為包裝器(Wrapper)
適配器模式屬于結(jié)構(gòu)型模式(沒有產(chǎn)生什么新對(duì)象,只是結(jié)構(gòu)的一些調(diào)整,讓類與類之間可以協(xié)調(diào)工作)
工作原理
- 將一個(gè)類的接口轉(zhuǎn)換成另一種接口,目的是讓原本接口不兼容的類可以兼容
- 從用戶的角度看不到被適配者(A調(diào)用B,中間有個(gè)adpater,B屬于被適配者),是解耦的
- 用戶調(diào)用適配器轉(zhuǎn)化出來的目標(biāo)接口方法,適配器再調(diào)用被適配者的相關(guān)接口方法
- 用戶收到反饋結(jié)果,感覺只是和目標(biāo)接口交互
分類
- 類適配器模式
- 對(duì)象適配器模式
- 接口適配器模式
應(yīng)用場景
很多時(shí)候,我們加入項(xiàng)目組的時(shí)候,項(xiàng)目已經(jīng)有一定的代碼量了,或者部分代碼已經(jīng)在生產(chǎn)環(huán)境上面使用了,這些方法已經(jīng)經(jīng)過測(cè)試,在我們開發(fā)新的項(xiàng)目時(shí),可以將這些類作為組件重復(fù)利用,但是可能沒辦法直接調(diào)用,需要我們使用適配器模式來讓這些方法適配我們的現(xiàn)有項(xiàng)目。
疑問:直接修改之前的類不就可以了嗎,為啥要使用適配器模式?答:之前的類都比較完善了,如果在上面改亂了,出了bug不好調(diào)試,但是如果使用了適配器模式,那出了bug就知道是適配器的問題。此外,在Adapter模式中,并非一定需要現(xiàn)成的代碼。只要知道現(xiàn)有類的功能,就可以編寫出新的類。
適配器模式還能用于做新舊版本的兼容,可以讓新版本扮演Adaptee 角色,舊版本扮演Target角色。接著編寫一個(gè)扮演Adapter 角色的類,讓它使用新版本的類來實(shí)現(xiàn)舊版本的類中的方法。如下圖
案例
類適配器模式
例1
介紹
- Adapter類,通過繼承src類,實(shí)現(xiàn) dst 類接口,完成src->dst的適配
- 如生活中的小例子:充電器本身相當(dāng)于Adapter,220V交流電相當(dāng)于src(即被適配者),我們的dst(即目標(biāo))是5V直流電
類圖
代碼實(shí)現(xiàn)
【被適配的類】
package com.atguigu.adapter.classadapter;
/**
* 被適配的類
*/
public class Voltage220V {
/**
* 輸出220V的電壓
* @return
*/
public int output220V() {
int src = 220;
System.out.println("電壓=" + src + "伏");
return src;
}
}
【適配接口】
package com.atguigu.adapter.classadapter;
/**
* 適配接口
*/
public interface IVoltage5V {
public int output5V();
}
【適配器】
package com.atguigu.adapter.classadapter;
/**
* 適配器類 將220V轉(zhuǎn)化為5V
*/
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//獲取到220V電壓
int srcV = output220V();
//轉(zhuǎn)成 5v(降壓)
int dstV = srcV / 44 ;
return dstV;
}
}
【手機(jī)類】
package com.atguigu.adapter.classadapter;
public class Phone {
/**
* 手機(jī)充電方法
* @param iVoltage5V
*/
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("電壓為5V, 可以充電~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("電壓過高, 不能充電~~");
}
}
}
【客戶端:用戶給手機(jī)充電】
package com.atguigu.adapter.classadapter;
public class Client {
public static void main(String[] args) {
System.out.println(" === 類適配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
【運(yùn)行】
=== 類適配器模式 ====
電壓=220伏
電壓為5V, 可以充電~~
Process finished with exit code 0
優(yōu)缺點(diǎn)分析
【優(yōu)點(diǎn)】
- 由于其繼承了src類,所以它可以根據(jù)需求重寫src類的方法,使得Adapter的靈活性增強(qiáng)了
【缺點(diǎn)】
- Java是單繼承機(jī)制,所以類適配器需要繼承src類這一點(diǎn)算是一個(gè)缺點(diǎn)(之前的七大原則分析過,使用繼承不好),因?yàn)檫@要求dst必須是接口,有一定局限性
- src類的方法在Adapter中都會(huì)暴露出來,也增加了使用的成本(如果方法很多,調(diào)用起來就麻煩了)
例2
類圖
代碼實(shí)現(xiàn)
【src】
package com.atguigu.adapter.Sample1;
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
【dst】
package com.atguigu.adapter.Sample1;
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
【適配器】
package com.atguigu.adapter.Sample1;
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
public void printWeak() {
showWithParen();
}
public void printStrong() {
showWithAster();
}
}
【客戶端】
package com.atguigu.adapter.Sample1;
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
【運(yùn)行】
(Hello)
*Hello*
Process finished with exit code 0
【分析】
對(duì)Main類的代碼而言,Banner類、showWithParen方法和showWithAster 方法被完全隱藏起來了。這就好像筆記本電腦只要在直流12伏特電壓下就能正常工作,但它并不知道這12伏特的電壓是由適配器將100伏特交流電壓轉(zhuǎn)換而成的。Main類并不知道PrintBanner類是如何實(shí)現(xiàn)的,這樣就可以在不用對(duì)Main類進(jìn)行修改的情況下改變PrintBanner類的具體實(shí)現(xiàn)。
對(duì)象適配器模式(常用方式)
例1
介紹
- 基本思路和類的適配器模式相同,只是將Adapter類作修改,不是繼承src類,而是持有src類的實(shí)例(聚合src類),以解決兼容性的問題。 即:持有 src類,實(shí)現(xiàn) dst 類接口,完成src->dst的適配
- 根據(jù)“合成復(fù)用原則”,在系統(tǒng)中盡量使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系
- 對(duì)象適配器模式是適配器模式常用的一種
類圖
代碼實(shí)現(xiàn)
【適配器類】
package com.atguigu.adapter.objectadapter;
/**
* 適配器類
*/
public class VoltageAdapter implements IVoltage5V {
/**
* 關(guān)聯(lián)關(guān)系-聚合
*/
private Voltage220V voltage220V;
/**
* 通過構(gòu)造器,傳入一個(gè) Voltage220V 實(shí)例
*
* @param voltage220v
*/
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
//獲取220V 電壓
int src = voltage220V.output220V();
System.out.println("使用對(duì)象適配器,進(jìn)行適配~~");
dst = src / 44;
System.out.println("適配完成,輸出的電壓為=" + dst);
}
return dst;
}
}
【客戶端:用戶給手機(jī)充電】
package com.atguigu.adapter.objectadapter;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(" === 對(duì)象適配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
其他類和類適配器模式的一致
優(yōu)缺點(diǎn)分析
【優(yōu)點(diǎn)】
- 對(duì)象適配器和類適配器其實(shí)算是同一種思想,只不過實(shí)現(xiàn)方式不同。根據(jù)合成復(fù)用原則,使用組合替代繼承,所以它解決了類適配器必須繼承src的局限性問題,也不再要求dst必須是接口
- 使用成本更低,更靈活
例2
代碼實(shí)現(xiàn)
【dst】
package com.atguigu.adapter.Sample2;
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
【適配器】
package com.atguigu.adapter.Sample2;
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string) {
this.banner = new Banner(string);
}
public void printWeak() {
banner.showWithParen();
}
public void printStrong() {
banner.showWithAster();
}
}
其他兩個(gè)類的和類適配器模式的例2的代碼一致
接口適配器模式
介紹
- 一些書籍稱為:適配器模式(Default Adapter Pattern)或缺省適配器模式
- 當(dāng)不需要全部實(shí)現(xiàn)接口提供的方法時(shí),可先設(shè)計(jì)一個(gè)抽象類實(shí)現(xiàn)接口,并為該接口中每個(gè)方法提供一個(gè)
默認(rèn)實(shí)現(xiàn)(空方法)
,那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實(shí)現(xiàn)需求 - 適用于不想使用一個(gè)接口所有的方法的情況
類圖
代碼實(shí)現(xiàn)
【接口】
package com.atguigu.adapter.interfaceadapter;
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
【抽象類:適配器】
package com.atguigu.adapter.interfaceadapter;
/**
* 在AbsAdapter 我們將 Interface4 的方法進(jìn)行默認(rèn)實(shí)現(xiàn)(空實(shí)現(xiàn))
*/
public abstract class AbsAdapter implements Interface4 {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
【客戶端:重寫抽象類的方法】
package com.atguigu.adapter.interfaceadapter;
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
/**
* 只需要去覆蓋我們 需要使用 接口方法
*/
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
登場角色
- Target(對(duì)象):負(fù)責(zé)定義所需的方法,如需要能提供5v電壓的方法
- Adaptee(被適配):該角色持有既定方法,如有一個(gè)方法可以提供220v電壓
- Adapter(適配):使用Adaptee角色的方法來滿足Target角色的需求
- Client(請(qǐng)求者):負(fù)責(zé)使用 Target 角色所定義的方法來做事,如使用5V電壓給手機(jī)充電
類圖
類適配器模式
對(duì)象適配器模式
適配器模式在SpringMvc中的應(yīng)用
- SpringMvc中的
HandlerAdapter
使用了適配器模式
類圖
模擬實(shí)現(xiàn)
【Controller】
package com.atguigu.spring.springmvc;
/**
* 多種Controller實(shí)現(xiàn)
*/
public interface Controller {
}
//-----------------針對(duì)不同的請(qǐng)求,有多種不同的controller------------------------
class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}
class SimpleController implements Controller {
public void doSimplerHandler() {
System.out.println("simple...");
}
}
class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}
【適配器】
package com.atguigu.spring.springmvc;
/**
* 定義一個(gè)Adapter接口
*/
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}
//-------------------多種適配器類-------------------------
class SimpleHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((SimpleController) handler).doSimplerHandler();
}
/**
* 判斷是否支持該handler
* @param handler
* @return
*/
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
}
class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController) handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController) handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
【servlet】文章來源:http://www.zghlxwxcb.cn/news/detail-605061.html
package com.atguigu.spring.springmvc;
import java.util.ArrayList;
import java.util.List;
public class DispatchServlet {
/**
* 組合所有適配器
*/
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
public DispatchServlet() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch() {
// 此處模擬SpringMVC從request取handler的對(duì)象,
// 適配器可以獲取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
//SimpleController controller = new SimpleController();
// 得到對(duì)應(yīng)適配器
HandlerAdapter adapter = getHandler(controller);
// 通過適配器執(zhí)行對(duì)應(yīng)的controller對(duì)應(yīng)方法
adapter.handle(controller);
}
/**
* 根據(jù)controller返回對(duì)應(yīng)的適配器
* @param controller
* @return
*/
public HandlerAdapter getHandler(Controller controller) {
//遍歷:根據(jù)得到的controller(handler), 返回對(duì)應(yīng)適配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}
public static void main(String[] args) {
new DispatchServlet().doDispatch(); // http...
}
}
分析
- 適配器的價(jià)值:controller變化了,適配器也會(huì)跟著變化,最終調(diào)用的方法也不同
- 擴(kuò)展Controller 時(shí),只需要增加一個(gè)適配器類就完成了SpringMVC的擴(kuò)展了
- 如果不使用適配器:可以看到處理器的類型不同,有多重實(shí)現(xiàn)方式,那么調(diào)用方式就不是確定的,如果需要直接調(diào)用Controller方法,需要調(diào)用的時(shí)候就得不斷是使用if else來進(jìn)行判斷是哪一種子類然后執(zhí)行。那么如果后面要擴(kuò)展Controller,就得修改原來的代碼,這樣違背了OCP原則
文章來源地址http://www.zghlxwxcb.cn/news/detail-605061.html
總結(jié)
- 類適配器:以類給到,在Adapter里,就是將src當(dāng)做類,繼承
- 對(duì)象適配器: 以對(duì)象給到,在Adapter里,將src作為一個(gè)對(duì)象,持有(聚合)
- 接口適配器: 以接口給到,在Adapter里,將src作為一個(gè)接口,實(shí)現(xiàn)
- Adapter模式最大的作用是將原本不兼容的接口融合在一起工作
- 實(shí)際開發(fā)中,實(shí)現(xiàn)起來不拘泥于上述三種經(jīng)典形式
文章說明
- 本文章為本人學(xué)習(xí)尚硅谷的學(xué)習(xí)筆記,文章中大部分內(nèi)容來源于尚硅谷視頻(點(diǎn)擊學(xué)習(xí)尚硅谷相關(guān)課程),也有部分內(nèi)容來自于自己的思考,發(fā)布文章是想幫助其他學(xué)習(xí)的人更方便地整理自己的筆記或者直接通過文章學(xué)習(xí)相關(guān)知識(shí),如有侵權(quán)請(qǐng)聯(lián)系刪除,最后對(duì)尚硅谷的優(yōu)質(zhì)課程表示感謝。
- 本人還同步閱讀《圖解設(shè)計(jì)模式》書籍(圖解設(shè)計(jì)模式/(日)結(jié)城浩著;楊文軒譯–北京:人民郵電出版社,2017.1),進(jìn)而綜合兩者的內(nèi)容,讓知識(shí)點(diǎn)更加全面
到了這里,關(guān)于【設(shè)計(jì)模式——學(xué)習(xí)筆記】23種設(shè)計(jì)模式——適配器模式Adapter(原理講解+應(yīng)用場景介紹+案例介紹+Java代碼實(shí)現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!