资讯专栏INFORMATION COLUMN

设计模式---适配器模式

Y3G / 2096人阅读

摘要:根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联聚合关系在类适配器模式中,适配器与适配者之间是继承或实现关系。


适配器概念介绍

1、不同国家的插座是有区别的,如果我们去国外旅游,需要带上国外的插头转换器,来能兼容国外的插座;

2、手机的耳机孔有圆头和扁头,如果扁头的耳机孔想接圆头的耳机就需要一个耳机的转换器;


上述所说的转换器,其实就是适配器;它是用来做兼容的;


介绍

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联(聚合)关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。


角色

Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

Adaptee(适配者类—适配接口):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。


工作原理

  • 适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;
  • 从用户的角度看不到被适配者;
  • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;

3种适配器模式

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

类适配器模式演示

以生活中充电器为例,充电器本身相当于适配者 (Adapter),220V 交流电相当于被适配者,我们的目标(target) 想把220V交流电转成5V直流电

要适配的类,即需要将220v电压转化为5v电压

//中国的电压220Vpublic class ChinaPower{    private final Integer outPut=220;    public Integer getOutPut() {        return outPut;    }}

适配器接口,只负责定义转化需要使用的业务逻辑方法,具体实现交由适配器类完成

//将电压转化为5v---适配接口public interface TransTo5V{    Integer transTo5V();}

适配器类,继承了ChinaPower,并实现了适配器接口,负责实现讲220v电压转化为5v的具体业务逻辑代码实现

//适配器类---实现适配器接口public class ChinaAdapter extends ChinaPower implements TransTo5V{    //将220v电压转换为5v的    @Override    public Integer transTo5V()    {        //获得被适配类,即我们需要将220v电压转化为5v返回        Integer output=super.getOutPut();        //进行电压转换操作        return output/44;    }}

Phone类,需要用到适配器进行兼容,这样才可以充电

//手机需要5v的电压进行充电public class Phone{    //通过适配器获得5v的电压    public void charging(ChinaAdapter chinaAdapter)    {        if(5==chinaAdapter.transTo5V())        {            System.out.println("得到5v,充电中...");        }        else        {            System.out.println("电压过高,手机压力过大");        }    }}

充电测试

public class test{    @Test    public void test()    {        Phone p=new Phone();        p.charging(new ChinaAdapter());    }}


对象适配器模式

还是以上面的例子为例,这一次适配器类不再继承ChinaPower ,而是以聚合的方式来代替继承,符合设计模式中的"合成复用原则";java是单继承机制,这样可以保留对象继承权;

我们只需要修改适配器类即可:

//适配器类---实现适配器接口public class ChinaAdapter implements TransTo5V{    private ChinaPower chinaPower;    //通过构造器,完成赋值    public ChinaAdapter(ChinaPower chinaPower)    {     this.chinaPower=chinaPower;       }    //将220v电压转换为5v的    @Override    public Integer transTo5V()    {        //获得被适配类,即我们需要将220v电压转化为5v返回        Integer output=chinaPower.getOutPut();        //进行电压转换操作        return output/44;    }}

测试

public class test{    @Test    public void test()    {        Phone p=new Phone();        p.charging(new ChinaAdapter(new ChinaPower()));    }}

对象适配器的优点

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承被适配者的局限性问题;


接口适配器模式

  • 接口适配器模式(Default Adapter Pattern),也叫缺省适配器模式;
  • 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;
  • 适用于一个接口不想使用其所有的方法的情况;

定义一个适配器接口:

public interface InterfaceTest {	public void m1();	public void m2();	public void m3();	public void m4();}

抽象类 AbsAdapter 将 InterfaceTest 的方法进行默认实现,当子类需要使用适配器接口中的某个方法,而不是全部方法时,就可以通过继承抽象类,来完成对需要使用的特定方法重写操作即可,无需实现适配器接口里面的全部方法

public abstract class AbsAdapter implements InterfaceTest {	//默认实现	public void m1() {}	public void m2() {}	public void m3() {}	public void m4() {}}

Client 调用接口,重写适配器抽象类方法

public class Client {	public static void main(String[] args) {				AbsAdapter absAdapter = new AbsAdapter() {			//只需要去覆盖我们 需要使用 接口方法			@Override			public void m1() {				System.out.println("使用了m1的方法");			}		};				absAdapter.m1();	}}

综合小案例—使用类适配器模式

power–带转换的电压

一个顶层接口Power

public interface Power{     Integer getOutPut();}

分支一: 中国的220v电压

//中国的电压220Vpublic class ChinaPower implements Power{    private final Integer outPut=220;    @Override    public Integer getOutPut() {        return outPut;    }}

分支二: 日本的110v电压

//日本电压110vpublic class JapenPower implements Power{    private final Integer output=110;    @Override    public Integer getOutPut() {        return output;    }}

adapter–适配器

失配器接口–DC5Adapter

//适配器接口public interface DC5Adapter{    boolean support(Power power);    Integer transTo5V(Power power);}

适配器类—ChinaAdapter–只负责将220v电压进行转换的工作

//该适配器负责将中国的220v电压转化为5v的电压public class ChinaAdapter implements DC5Adapter{    //当前适配器只负责将220v电压转化为5v的功能实现    private  static Integer voltage=220;    //判断当前适配器能否胜任传入power电压的转化职责    @Override    public boolean support(Power power)    {      if(power.getOutPut().equals(voltage))      return true;      return false;    }    //将220v电压转换为5v的    @Override    public Integer transTo5V(Power power)    {        //获得被适配类,即我们需要将220v电压转化为5v返回        Integer output=power.getOutPut();        //进行电压转换操作        return output/44;    }}

适配器类—JapenAdapter–只负责将110v电压进行转换的工作

//该适配器负责将日本的110v电压转化为5v的电压public class JapenAdapter implements DC5Adapter{    //当前适配器只负责将110v电压转化为5v的功能实现    private  static Integer voltage=110;    //判断是否支持将日本110v电压转化为5v电压的操作    @Override    public boolean support(Power power) {        if(power.getOutPut()==voltage)            return true;        return false;    }    //将110v电压转换为5v的    @Override    public Integer transTo5V(Power power)    {        //获得被适配类,即我们需要将110v电压转化为5v返回        Integer output=power.getOutPut();        //进行电压转换操作        return output/22;    }}

FindAdapter–寻找合适的适配器

//手机需要5v的电压进行充电public class FindAdapter{    //存放所有适配器的set集合    private static final Set<DC5Adapter> DC5Adapters=new HashSet<>();    //通过静态代码块进行初始化操作    static    {        DC5Adapters.add(new ChinaAdapter());        DC5Adapters.add(new JapenAdapter());    }    // 根据电压找合适的变压器    public DC5Adapter getPowerAdapter(Power power)    {        DC5Adapter dc5Adapter=null;        for(DC5Adapter da:DC5Adapters)        {            //如果遍历到当前电压合适的变压器就直接退出遍历            if(da.support(power))            {                dc5Adapter=da;                break;            }        }        //如果遍历完所有的变压器都没有找到合适的,就抛出异常        if(dc5Adapter==null)        {            throw  new IllegalArgumentException("未能找到合适的变压器");        }        //返回找到的合适的变压器        return dc5Adapter;    }}

测试

public class test{    @Test    public void test()    {        //找寻合适的变压器是第一步        FindAdapter fa=new FindAdapter();        //找寻可以将220v转化为5v的变压器,即适配器        DC5Adapter adapter = fa.getPowerAdapter(new ChinaPower());        //输出当前变压器转化之后的电压        System.out.println(adapter.transTo5V(new ChinaPower()));    }}


适配器模式总结

主要优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

具体来说,类适配器模式还有如下优点:

  • 由于适配器类是适配者类(适配器接口或适配器接口实现的抽象类)的子类,因此可以在适配器类中置换一些适配者(适配器接口或适配器接口实现的抽象类)的方法,使得适配器的灵活性更强。
  • 一个对象适配器可以把多个不同的适配者(适配器接口或适配器接口实现的抽象类)适配到同一个目标;
  • 可以适配一个适配者的子类,由于适配器和适配者(适配器接口或适配器接口实现的抽象类)之间是关联关系,根据“里氏代换原则”,适配者(适配器接口或适配器接口实现的抽象类)的子类也可通过该适配器进行适配。

主要缺点

  • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类(适配器接口或适配器接口实现的抽象类),不能同时适配多个适配者;
  • 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
  • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

spring MVC中的适配器模式

springMVC处理请求流程

  • 第1步 用户发送请求至前端控制器DispatcherServlet;
  • 第2,3步DispatcherServlet收到请求,根据请求url调用HandlerMapping处理器映射器找到具体的处理器;生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
  • 第4步 DispatcherServlet通过HandlerAdapter处理器适配器调用具体的处理器;(这一步用到适配者模式)
  • 第5,6步 执行处理器(Controller,也叫后端控制器),返回ModelAndView;
  • 第7步 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  • 第8步DispatcherServlet将ModelAndView传给ViewReslover视图解析器(但是如果加上@responsebody注解,则返回值不通过viewResolver,而是直接返回object);
  • 第9步 ViewReslover解析后返回具体View;
  • 第10步 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中);
  • 第11步 DispatcherServlet响应用户。

SpringMVM 中的 HandlerAdapter(上图的第4步), 就使用了适配器模式;


请求处理方法中适配器模式部分源码探究

Spring MVC中的适配器模式主要用于执行目标 Controller 中的请求处理方法。

在Spring MVC中,DispatcherServlet 作为用户,HandlerAdapter 作为期望接口(适配器接口),具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){     ((MultiActionController)mappedHandler.getHandler()).xxx  }else if(mappedHandler.getHandler() instanceof XXX){      ...  }else if(...){     ...  }  

这样假设如果我们增加一个 HardController,就要在代码中加入一行 if(mappedHandler.getHandler() instanceof HardController),这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。

我们来看看源码,首先是适配器接口 HandlerAdapter

//适配器接口public interface HandlerAdapter {    //判断当前的controller请求是否能被当前的适配器类处理    boolean supports(Object var1);            //只有当支持处理当前请求后,才会执行下面的处理请求方法,返回一个ModelAndView对象     ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;    long getLastModified(HttpServletRequest var1, Object var2);}

现该接口的适配器每一个 Controller 都有一个适配器与之对应,这样的话,每自定义一个 Controller 需要定义一个实现 HandlerAdapter 的适配器。

springmvc 中提供的 Controller 实现类有如下:

springmvc 中提供的 HandlerAdapter 实现类如下

HttpRequestHandlerAdapter 这个适配器代码如下:

//不同的适配器类实现不同的功能//当前的HttpRequestHandlerAdapter 适配器类,只负责处理关于HttpRequest相关的请求public class HttpRequestHandlerAdapter implements HandlerAdapter {    public HttpRequestHandlerAdapter() {    }        //判断当前的controller请求是否是HttpRequestHandler类型的    //当前适配器只支持处理当前类型的handler     public boolean supports(Object handler) {        return handler instanceof HttpRequestHandler;    }    //如果验证支持,会调用下面这个方法进行具体逻辑处理    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {    //先进行强制类型转换,转换为指定的handler类型,然后就可以调用该类型处理对应请求的方法了    //调用HttpRequestHandler的handleRequest处理对应的请求        ((HttpRequestHandler)handler).handleRequest(request, response);        return null;    }    public long getLastModified(HttpServletRequest request, Object handler) {        return handler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L;    }}

当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet 会通过 handler 的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的 hanle() 方法来调用 Controller 中的用于处理请求的方法。

public class DispatcherServlet extends FrameworkServlet {//用于存放所有HandlerAdapter适配器类的list集合    private List<HandlerAdapter> handlerAdapters;        //初始化handlerAdapters    private void initHandlerAdapters(ApplicationContext context) {        //..省略...    }        // 遍历所有的 HandlerAdapters,通过 supports 判断找到匹配的适配器    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {		for (HandlerAdapter ha : this.handlerAdapters) {			if (logger.isTraceEnabled()) {				logger.trace("Testing handler adapter [" + ha + "]");			}			if (ha.supports(handler)) {				return ha;			}		}	}		// 分发请求,请求需要找到匹配的适配器来处理	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {		HttpServletRequest processedRequest = request;
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/118905.html

相关文章

  • PHP设计模式(十):配器模式

    摘要:原文地址设计模式十适配器模式在设计模式七设计模式分类中我们提到过结构设计模式,结构设计模式专注于设计对象和实例的构建组合过程。适配器模式在不修改现有代码的基础上,保留了架构。 原文地址:PHP设计模式(十):适配器模式 Introduction 在PHP设计模式(七):设计模式分类中我们提到过结构设计模式(Structural patterns),结构设计模式专注于设计对象(Objec...

    paney129 评论0 收藏0
  • 一天一个设计模式之JS实现——配器模式

    摘要:本文参考于设计模式课程设计模式之适配器模式设计模式是一套被反复使用的多数人知晓的经过分类编目的代码设计经验的总结。第一个设计模式是适配器模式。总的来说适配器就是的模式,与修饰模式直接无感使用不同,适配器模式使用对象变为。 本文参考于:设计模式课程设计模式之适配器模式 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被...

    Aceyclee 评论0 收藏0
  • 深入理解配器模式

    摘要:组件目标角色客户端所期待得到的接口,适配器角色适配器类是本模式的核心。最后附上一张适配器的概览图参考适配器模式与模式之适配器模式基础浅谈常见设计模式适配器模式原理及实例介绍适配器模式装饰模式代理模式的异同 适配器模式 说到适配器,小秋第一想到的是我们电脑的电源适配器,它能够将220v,110v等不同电压转换为我们电脑需要的电压值供电脑使用,那么你知道适配器模式是怎样实现的吗,下面就和小...

    Rindia 评论0 收藏0
  • JavaScript 设计模式(四):适配模式

    摘要:与其它模式的异同适配器模式不会改变原有接口,这一点与装饰者模式和代理模式类似。代理模式适配器模式与代理模式最相似,同样都是创建一个新对象包装一次,实现对本体的调用。外观模式外观模式与适配器模式最大的区别,是定义了一个新的接口。 showImg(https://segmentfault.com/img/bVbul8d?w=800&h=600); 适配器模式:将一个类(对象)的接口(方法或...

    MingjunYang 评论0 收藏0
  • 浅谈25种设计模式(4/25)(此坑未填)

    摘要:适配器模式桥接模式过滤器模式组合模式装饰器模式外观模式享元模式代理模式行为型模式这些设计模式特别关注对象之间的通信。对象适配器另外一种适配器模式是对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式。 设计模式汇总 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需...

    0xE7A38A 评论0 收藏0

发表评论

0条评论

Y3G

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<