介绍
生活中的案例
不同国家的插座不同,出国旅游充电器不能直接使用,可以通过使用多功能转换插头
来辅助使用
基础介绍
-
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
适配器模式属于结构型模式(没有产生什么新对象,只是结构的一些调整,让类与类之间可以协调工作)
工作原理
- 将一个类的接口转换成另一种接口,目的是让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者(A调用B,中间有个adpater,B属于被适配者),是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
分类
- 类适配器模式
- 对象适配器模式
- 接口适配器模式
应用场景
很多时候,我们加入项目组的时候,项目已经有一定的代码量了,或者部分代码已经在生产环境上面使用了,这些方法已经经过测试,在我们开发新的项目时,可以将这些类作为组件重复利用,但是可能没办法直接调用,需要我们使用适配器模式来让这些方法适配我们的现有项目。
疑问:直接修改之前的类不就可以了吗,为啥要使用适配器模式?答:之前的类都比较完善了,如果在上面改乱了,出了bug不好调试,但是如果使用了适配器模式,那出了bug就知道是适配器的问题。此外,在Adapter模式中,并非一定需要现成的代码。只要知道现有类的功能,就可以编写出新的类。
适配器模式还能用于做新旧版本的兼容,可以让新版本扮演Adaptee 角色,旧版本扮演Target角色。接着编写一个扮演Adapter 角色的类,让它使用新版本的类来实现旧版本的类中的方法。如下图
案例
类适配器模式
例1
介绍
- Adapter类,通过继承src类,实现 dst 类接口,完成src->dst的适配
- 如生活中的小例子:充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的dst(即目标)是5V直流电
类图
代码实现
【被适配的类】
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转化为5V
*/
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
//转成 5v(降压)
int dstV = srcV / 44 ;
return dstV;
}
}
【手机类】
package com.atguigu.adapter.classadapter;
public class Phone {
/**
* 手机充电方法
* @param iVoltage5V
*/
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压过高, 不能充电~~");
}
}
}
【客户端:用户给手机充电】
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());
}
}
【运行】
=== 类适配器模式 ====
电压=220伏
电压为5V, 可以充电~~
Process finished with exit code 0
优缺点分析
【优点】
- 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了
【缺点】
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点(之前的七大原则分析过,使用继承不好),因为这要求dst必须是接口,有一定局限性
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本(如果方法很多,调用起来就麻烦了)
例2
类图
代码实现
【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();
}
}
【运行】
(Hello)
*Hello*
Process finished with exit code 0
【分析】
对Main类的代码而言,Banner类、showWithParen方法和showWithAster 方法被完全隐藏起来了。这就好像笔记本电脑只要在直流12伏特电压下就能正常工作,但它并不知道这12伏特的电压是由适配器将100伏特交流电压转换而成的。Main类并不知道PrintBanner类是如何实现的,这样就可以在不用对Main类进行修改的情况下改变PrintBanner类的具体实现。
对象适配器模式(常用方式)
例1
介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例(聚合src类),以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系
- 对象适配器模式是适配器模式常用的一种
类图
代码实现
【适配器类】
package com.atguigu.adapter.objectadapter;
/**
* 适配器类
*/
public class VoltageAdapter implements IVoltage5V {
/**
* 关联关系-聚合
*/
private Voltage220V voltage220V;
/**
* 通过构造器,传入一个 Voltage220V 实例
*
* @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("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
【客户端:用户给手机充电】
package com.atguigu.adapter.objectadapter;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(" === 对象适配器模式 ====");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
其他类和类适配器模式的一致
优缺点分析
【优点】
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口
- 使用成本更低,更灵活
例2
代码实现
【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();
}
}
其他两个类的和类适配器模式的例2的代码一致
接口适配器模式
介绍
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个
默认实现(空方法)
,那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求 - 适用于不想使用一个接口所有的方法的情况
类图
代码实现
【接口】
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 的方法进行默认实现(空实现)
*/
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(对象):负责定义所需的方法,如需要能提供5v电压的方法
- Adaptee(被适配):该角色持有既定方法,如有一个方法可以提供220v电压
- Adapter(适配):使用Adaptee角色的方法来满足Target角色的需求
- Client(请求者):负责使用 Target 角色所定义的方法来做事,如使用5V电压给手机充电
类图
类适配器模式
对象适配器模式
适配器模式在SpringMvc中的应用
- SpringMvc中的
HandlerAdapter
使用了适配器模式
类图
模拟实现
【Controller】
package com.atguigu.spring.springmvc;
/**
* 多种Controller实现
*/
public interface Controller {
}
//-----------------针对不同的请求,有多种不同的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;
/**
* 定义一个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】
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的对象,
// 适配器可以获取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
//SimpleController controller = new SimpleController();
// 得到对应适配器
HandlerAdapter adapter = getHandler(controller);
// 通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
/**
* 根据controller返回对应的适配器
* @param controller
* @return
*/
public HandlerAdapter getHandler(Controller controller) {
//遍历:根据得到的controller(handler), 返回对应适配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}
public static void main(String[] args) {
new DispatchServlet().doDispatch(); // http...
}
}
分析
- 适配器的价值:controller变化了,适配器也会跟着变化,最终调用的方法也不同
- 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了
- 如果不使用适配器:可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则
总结
- 类适配器:以类给到,在Adapter里,就是将src当做类,继承
- 对象适配器: 以对象给到,在Adapter里,将src作为一个对象,持有(聚合)
- 接口适配器: 以接口给到,在Adapter里,将src作为一个接口,实现
- Adapter模式最大的作用是将原本不兼容的接口融合在一起工作
- 实际开发中,实现起来不拘泥于上述三种经典形式
文章说明
- 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
- 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面