适配器模式在项目中的应用

适配器概念

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。
src=http___images2

适配器模式可分为对象适配器和类适配器两种

  • 对象适配器模式

适配器与适配者之间是关联关系也可以称为组合关系

  • 类适配器模式

适配器与适配者之间是继承(或实现)关系

适配器特点

适配器模式主要有3个角色:

  1. 目标抽象类

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

  1. 适配器类

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

  1. 适配者类

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

适配器主要解决啥问题

适配器模式的诞生是为了解决需要合作的对象不兼容的问题。通过适配器在两个并不兼容的对象间搭起合作的桥梁,两个不兼容的对象在不直接接触的情况下,通过适配器完成合作。比如有以下问题:

  • 系统需要使用现有的类,而此类不符合系统需要,即接口不兼容。
  • 需要建立一个可重复使用的类,用于与一些彼此间没有太大关联的类合作。
  • 需要统一的输出接口,但是输出的类型不可预知。

常见的使用场景

  • 关于适配器模式的使用场景,一般主要是当我们需要修改一些正在运行着的代码,并且希望可以复用原有代码实现新的功能的时候,就要考虑适配器模式。
  • 在Spring框架中,就大量的使用了适配器模式,读者可以打开自己的IDE,尝试着以关键字”Adapter”全局搜索下,一定会有很多的实际应用。

这么说可能大家不好理解,稍后我们用代码举例一一说明

oo设计原则

  1. 单一职责原则:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。就是平时我们说的高内聚、低耦合理论指导依据。

  2. 开闭原则:指软件实体应尽量在不修改原有代码的情况下进行扩展。

  3. 接口隔离原则:控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来不方便。

  4. 里氏替换原则:应该将父类设计为抽象类或者接口,让子类继承父类或实现父类接口,并实现在父类中声明的方法。

  5. 依赖倒转原则:要针对接口编程,不要针对实现编程。

  6. 合成复用原则:优先使用对象组合,而不是继承来达到复用的目的。

适配器模式就很好的体现了开闭原则,另外在适配器模式中尽量使用组合也就是对象适配器模式,少用继承或实现,也就是合成复用原则的具体要求。

代码例子

示例1

  1. 我们定义个接口比如就是我们经常在web开发中的service接口,并随便写一个方法。我们就随便写一个:
interface Param1{
    void doingThings();
}
复制代码
  1. 再定义一个上面接口的实现类
class Param1Impl implements Param1{
    @Override
    public void doingThings() {
        System.out.println("我正在干活");
    }
}
复制代码
  1. 我们再定义一个方法入参是接口类型的对象来调用对应的实现类中的方法。
public static void work(Param1 param1){
    System.out.println("准备开始干活");
    param1.doingThings();
    System.out.println("干完活了");
}
复制代码
  1. 以上三步我们定义完就可以测试一下:
public static void main(String[] args) {
    Param1 param1=new Param1Impl();
    work(param1);
}
复制代码

输出结果:
准备开始干活
我正在干活
干完活了

好了以上是我们最常见的代码。如果这个代码作为历史代码,现在突然有个新的需求,有个新的接口Param2和对应的实现Param2Impl这两个类作为一个新逻辑想复用以前的历史代码逻辑。也就是想复用上面代码中的work方法。对应的Param2的接口代码示例:

interface Param2{
    void doingThings();
}

class Param2Impl implements Param2{

    @Override
    public void doingThings() {
        System.out.println("Param2正在干活");
    }
}
复制代码

基于上面的新流程就出现了问题,因为上面的work方法中所接收的参数类型是Param1所以对于新的流程对应的类型是Param2所以表面上看根本无法复用之前的work方法。这个时候就需要用一个中间层进行转换一下最终让work方法能兼容新逻辑。我们需要增加一个适配器类:

class Adapter implements Param1{

    private Param2 param2;

    public Adapter(Param2 param2) {
        this.param2 = param2;
    }

    @Override
    public void doingThings() {
        param2.doingThings();
    }
}

复制代码

如上的适配器类,想要适配原来的work方法那么适配器就要伪装成原来逻辑的入参类型,也就是我们示例中的Param1。我们在模拟调用一下:

Param2 param2=new Param2Impl();
Adapter adapter = new Adapter(param2);
work(adapter);
复制代码

结果

准备开始干活
Param2正在干活
干完活了

这样就满足了我们的要求,将新逻辑复用了老的方法。

示例2

我们定义动物行为的一个接口

interface Animal{
    public void run();
    public void sing();
    public void swim();
}
复制代码

接着我们用一个类去实现这个接口

class Dog implements Animal{

    @Override
    public void run() {

    }

    @Override
    public void sing() {

    }

    @Override
    public void swim() {

    }
}
复制代码

但是这个类不想实现接口中的sing()方法,但是实现接口的时候还必须都要把抽象方法实现。在这种情况下我们就需要弄一个适配器类来转接一下。

abstract class Afunc implements Animal{
    @Override
    public void run() {

    }

    @Override
    public void sing() {

    }

    @Override
    public void swim() {

    }
}
复制代码

这个就是一个适配器,实现接口并将接口中方法进行空实现。
接下来我们让类去继承这个适配器,同时可以实现重写自己需要的方法。

class bird extends Afunc{
    @Override
    public void sing() {
        super.sing();
    }
}
复制代码

其实用一个现实生活中的例子来说明,比如说你开发一片房地产,接口相当于草图,他决定了你建出来的东西大概是个什么样子,外形什么样,有多大多高。实现接口的抽象类是个半成品,你已经完成了部分工作,比如内部的空间设计,户型设计,这也决定了你到底建的是个商场,还是住宅区,还是别的什么东西。但是这个程度肯定是不能拿去用的,无论是住宅区还是商场,每个户主都有自己的喜好,这就需要去装修,装修成自己喜欢的样子,就可以用了,这就是最后具体的实现类。

这样就满足了我们的需求。

适配器模式的体现

许多框架或者JDK中适配器模式也有很多的应用,这里我们就进行简单介绍:

InputStreamReader

是字节转换为字符的桥梁,它使指定的charset读取字节并将其解码为字符。(解码:把看不懂的字节变成可以看懂的字符)

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
复制代码

我们经常看到如上的代码。System.in 实际是类型为 InputStream,而由于 BufferedReader 与 InputStream 不能一起工作,于是引入 BufferedReader 类,作为适配器类,将 InputStreamReader 类的接口转成 BufferedReader 类可用的接口。

Spring MVC中的适配器模式

我们使用springmvc进行开发的时候写过很多controller,不同类型的Controller通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet直接获取对应类型的Controller,就得需要的自行来判断。那就可能需要写很多if-else,比如:

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

如果这样设计那么我们每写一个controller那就要在下面写一个else-if。这样也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。接下来我们自己模拟一下spring是怎么通过HandlerAdapter来实现映射controller的。由于直接贴spring代码有些其它逻辑干扰主流程。我们下面自己把核心流程抽出来进行讲解:

  1. 首先我们有个Controller接口
public interface Controller {  
  
}  
复制代码
  1. 接着有几个具体的Controller的具体实现
public class HttpController implements Controller{  
    public void doHttpHandler(){  
        System.out.println("http...");  
    }  
}  
  
public class SimpleController implements Controller{  
    public void doSimplerHandler(){  
        System.out.println("simple...");  
    }  
}  
  
public class AnnotationController implements Controller{  
    public void doAnnotationHandler(){  
        System.out.println("annotation...");  
    }  
} 
复制代码

其实这个就是模仿spring中的Controller实现,spring中提供的实现如图:
controller

如果代码只写到这,那就需要每次增加一个controller就需要写个if-else判断需要具体由那个controller实现类去处理。

进而我们采用了增加适配器类来解决这个问题:
3. 我们定义一个Adaper接口

public interface HandlerAdapter {  
    public boolean supports(Object handler);  
    public void handle(Object handler);  
}  
复制代码
  1. 编写适配器
public class SimpleHandlerAdapter implements HandlerAdapter {  
    public void handle(Object handler) {  
        ((SimpleController)handler).doSimplerHandler();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceof SimpleController);  
    }  
  
}  
  
public class HttpHandlerAdapter implements HandlerAdapter {  
  
    public void handle(Object handler) {  
        ((HttpController)handler).doHttpHandler();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceof HttpController);  
    }  
  
}  

public class AnnotationHandlerAdapter implements HandlerAdapter {  
  
    public void handle(Object handler) {  
        ((AnnotationController)handler).doAnnotationHandler();  
    }  
  
    public boolean supports(Object handler) {  
          
        return (handler instanceof AnnotationController);  
    }  
  
}  
复制代码

springmvc中提供的 HandlerAdapter 实现类如下
HandlerAdapter
5. 最后我们模拟一个DispatcherServlet

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的对象,仅仅new出,可以出,               
        //不论实现何种Controller,适配器总能经过适配以后得到想要的结果  
        //模拟具体的controller
        SimpleController controller = new SimpleController();  
        //得到对应适配器  
        HandlerAdapter adapter = getHandler(controller);  
        //通过适配器执行对应的controller对应方法  
        adapter.handle(controller);  
          
    }  
      
    public HandlerAdapter getHandler(Controller controller){  
        for(HandlerAdapter adapter: this.handlerAdapters){  
            if(adapter.supports(controller)){  
                return adapter;  
            }  
        }  
        return null;  
    }  
      
    public static void main(String[] args){  
        new DispatchServlet().doDispatch();  
    }  
      
}  
复制代码

这样就是整个springMVC的核心流程中应用的适配器模式的方式了。

实际项目场景中的应用

  • 先说背景:我们是属于中台服务。中台需要把各个业务线的各种类型服务做统一包装,再对外提供接口进行使用。而这在我们平常的开发中也是非常常见的。一个系统会接收各种各样的MQ消息或者接口,如果一个个的去开发,就会耗费很大的成本,同时对于后期的拓展也有一定的难度。此时就会希望有一个系统可以配置一下就把外部的MQ接入进行,这些新增加的MQ就像已经存在的逻辑一样继续使用。
  • 实现思路:对于新接入的业务线使用历史的MQ逻辑就需要一个适配器进行将新的业务线的属性适配到历史的业务属性,这样下游接受MQ的消费端改造成本也会大大下降,比如在适配器中将新业务线的A属性映射到历史MQ中对应的属性A1。

适配器模式配图
具体代码就不贴了。这个只是举个实际应用场景的例子。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享