前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式---适配器模式

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

作者头像
大忽悠爱学习
发布2021-11-15 15:25:07
6810
发布2021-11-15 15:25:07
举报
文章被收录于专栏:c++与qt学习c++与qt学习

适配器模式


适配器概念介绍

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

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

在这里插入图片描述
在这里插入图片描述

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


介绍

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

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

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


角色

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

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

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

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


工作原理

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

3种适配器模式

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

类适配器模式演示

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

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

代码语言:javascript
复制
//中国的电压220V
public class ChinaPower
{
    private final Integer outPut=220;
    public Integer getOutPut() {
        return outPut;
    }
}

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

代码语言:javascript
复制
//将电压转化为5v---适配接口
public interface TransTo5V
{
    Integer transTo5V();
}

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

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

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

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

    }
}

充电测试

代码语言:javascript
复制
public class test
{
    @Test
    public void test()
    {
        Phone p=new Phone();
        p.charging(new ChinaAdapter());
    }
}
在这里插入图片描述
在这里插入图片描述

对象适配器模式

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

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

代码语言:javascript
复制
//适配器类---实现适配器接口
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;
    }
}

测试

代码语言:javascript
复制
public class test
{
    @Test
    public void test()
    {
        Phone p=new Phone();
        p.charging(new ChinaAdapter(new ChinaPower()));
    }
}
在这里插入图片描述
在这里插入图片描述

对象适配器的优点

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


接口适配器模式

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

定义一个适配器接口:

代码语言:javascript
复制
public interface InterfaceTest {
	public void m1();
	public void m2();
	public void m3();
	public void m4();
}

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

代码语言:javascript
复制
public abstract class AbsAdapter implements InterfaceTest {

	//默认实现
	public void m1() {}
	public void m2() {}
	public void m3() {}
	public void m4() {}
}

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

代码语言:javascript
复制
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

代码语言:javascript
复制
public interface Power
{
     Integer getOutPut();
}

分支一: 中国的220v电压

代码语言:javascript
复制
//中国的电压220V
public class ChinaPower implements Power
{
    private final Integer outPut=220;
    @Override
    public Integer getOutPut() {
        return outPut;
    }
}

分支二: 日本的110v电压

代码语言:javascript
复制
//日本电压110v
public class JapenPower implements Power
{
    private final Integer output=110;
    @Override
    public Integer getOutPut() {
        return output;
    }
}

adapter–适配器

失配器接口–DC5Adapter

代码语言:javascript
复制
//适配器接口
public interface DC5Adapter
{
    boolean support(Power power);
    Integer transTo5V(Power power);
}

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

代码语言:javascript
复制
//该适配器负责将中国的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电压进行转换的工作

代码语言:javascript
复制
//该适配器负责将日本的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–寻找合适的适配器

代码语言:javascript
复制
//手机需要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;
    }
}

测试

代码语言:javascript
复制
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,需要的自行来判断,像下面这段代码一样:

代码语言:javascript
复制
if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...){  
   ...  
}  

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

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

代码语言:javascript
复制
//适配器接口
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 这个适配器代码如下:

代码语言:javascript
复制
//不同的适配器类实现不同的功能
//当前的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 中的用于处理请求的方法。

代码语言:javascript
复制
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;
		HandlerExecutionChain mappedHandler = null;

		//找到能处理当前processedRequest,即request请求的handler
		mappedHandler = getHandler(processedRequest);
			
		// 确定当前handler匹配的适配器类.
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		ha.getLastModified(request, mappedHandler.getHandler());
					
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    }
	// ...省略...
}	

通过适配器模式我们将所有的 controller 统一交给 HandlerAdapter 处理,免去了写大量的 if-else 语句对 Controller 进行判断,也更利于扩展新的 Controller 类型。


总结

在这里插入图片描述
在这里插入图片描述

使用 HandlerAdapter 的原因分析:

如果处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果直接调用 Controller 方法,就得不断使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则;

说明:

  • Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类;
  • 适配器代替 controller执行相应的方法;
  • 扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展;

参考文章

设计模式 8 - 适配器模式与springmvc源码分析 设计模式 | 适配器模式及典型应用 适配器模式(SpringMVC源码分析) 设计模式 | 适配器模式及典型应用

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-09-01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 适配器模式
  • 适配器概念介绍
  • 介绍
  • 角色
  • 工作原理
  • 3种适配器模式
    • 类适配器模式演示
      • 对象适配器模式
        • 对象适配器的优点
      • 接口适配器模式
        • 综合小案例—使用类适配器模式
          • power–带转换的电压
          • adapter–适配器
          • FindAdapter–寻找合适的适配器
          • 测试
      • 适配器模式总结
        • 主要优点
          • 主要缺点
            • 适用场景
            • spring MVC中的适配器模式
              • springMVC处理请求流程
                • 请求处理方法中适配器模式部分源码探究
                  • 总结
                  • 参考文章
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档