• 【设计模式】使用适配器模式做补偿设计


    1.概述

    适配器模式是一种结构型设计模式,它提供了一个中间层,通过这个中间层,客户端可以使用统一的接口与具有不同接口的类进行交互,也就是说,将一个接口转换成客户期望的另一个接口,使得原本不兼容的接口能够协同工作。

    举个现实中的例子,我们现在的很多轻薄笔记本为了减少厚度,一般不会设计网线的接口,或者说在笔记本上的可以插线的接口很少,这时候使用到的拓展坞就可以视为是一种适配器。
    在这里插入图片描述


    值得一提的是,与其他模式有点不同的是,适配器模式是一种补偿模式,主要用于解决现有的设计或实现与需求不匹配的问题,是在系统开发后期或者集成阶段,为了兼容已存在的、难以修改的组件接口或者为了统一不同接口之间的差异而采取的一种补救措施。

    2.两种适配器模式

    适配器模式有两种常见的实现形式:

    • 类适配器:通过继承的方式,子类(适配器)继承自需要被适配的类(适配者),并同时实现目标接口。
    • 对象适配器:通过组合的方式,适配器包含一个适配者的实例,并在自己的方法中调用适配者的功能来实现目标接口的方法。

    2.1.类适配器

    按照上面的描述,类图如下:
    在这里插入图片描述

    • Target:目标接口,适配器将不适用于当前系统的接口,转换给目标接口的形状。
    • Adaptee:被适配的类,也就是被转换的对象。
    • Adapter:适配器

    Client调用适配器,获取到转换成符合当前系统要求的数据。


    这么看可能有点抽象,我们通过一个简单的业务场景来理解一下这种模式:

    有一个客服系统,在了解到客户的需求后会往客户管理系统中推送线索,并且在客服系统中可以查看到销售对当前客户的跟进情况。现在由于旧客户管理系统日渐不满足使用要求了,于是建立了一个新的客户管理系统,客服系统需要从新的系统中获取到跟进数据。

    但是,新旧两个客户系统对于跟进日志的接口定义不一样,这时候又不想对客服系统做大的改动,就可以使用适配器对接口进行转换,下面是简化过后的代码。

    • 旧系统的接口定义
      /**
       * 跟进记录对象
       */
      @Data
      public class Record {
          /**
           * 跟进内容
           */
          private String followContent;
          /**
           * 附件地址
           */
          private String enclosure;
      }
      
      /**
       * 目标接口
       */
      public interface RecordService {
          Record getRecord();
      }
      
      /**
       * 目标接口实现
       */
      public class RecordServiceImpl implements RecordService {
          @Override
          public Record getRecord() {
              // 模拟从老系统获取数据
              Record record = new Record();
              record.setFollowContent("跟进内容(老系统)");
              record.setEnclosure("附件地址(老系统)");
              return record;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    • 新系统中的定义
    /**
     * 新系统沟通记录对象
     */
    @Data
    public class NewRecord {
        /**
         * 沟通内容
         */
        private String communicateContent;
    
        /**
         * 附件地址
         */
        private String accessory;
    }
    
    /**
     * 新系统接口
     */
    public interface NewRecordService {
        NewRecord getNewRecord();
    }
    
    /**
     * 新系统接口实现
     */
    public class newRecordServiceImpl implements NewRecordService{
        @Override
        public NewRecord getNewRecord() {
            // 模拟从新系统获取数据
            NewRecord newRecord = new NewRecord();
            newRecord.setCommunicateContent("跟进内容(新系统)");
            newRecord.setAccessory("附件地址(新系统)");
            return newRecord;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    实际情况相对于上面的代码可能会更加复杂,这里做演示就简化了一下代码,新旧系统主要是返回对象不一样,返回对象中的字段名不一样。

    确定了之后编写适配器,按照类适配器的定义,我们先继承新接口的实现,再实现旧的模板接口

    public class RecordAdapter extends newRecordServiceImpl implements RecordService {
    
        @Override
        public Record getRecord() {
            NewRecord newRecord = super.getNewRecord();
            Record record = new Record();
            record.setFollowContent(newRecord.getCommunicateContent());
            record.setEnclosure(newRecord.getAccessory());
            return record;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在适配器中,实现旧接口中的方法,并调用父类(新接口实现)的新的方法,然后将新接口返回的对象值封装到旧的日志对象中,做一下测试:

    public class RecordAdapterTest {
        @Test
        public void testGetRecord() {
            RecordService recordService = new RecordServiceImpl();
            System.out.println(recordService.getRecord());
            recordService = new RecordAdapter();
            System.out.println(recordService.getRecord());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Record(followContent=跟进内容(老系统), enclosure=附件地址(老系统))
    Record(followContent=跟进内容(新系统), enclosure=附件地址(新系统))

    可以看到,在字段名没变动的情况下,兼容了新系统的值。

    通过适配器的方式,不需要修改新系统的接口,也不需要修改客服系统的上层业务代码,只需要在获取数据这一层做一下转换即可,返回给前端后,前端也不需要重新匹配字段,减少了代码的修改范围,降低了风险。


    类适配器对这种简单的转换用起来比较方便,但是也存在比较大的缺陷:

    • 继承带来的常见问题,父类发生变化时,子类可能也需要被迫的跟着变化。
    • 对于Java这样的单继承语言来说,面对有多个需要被转换的对象时,就显得有点力不从心了。

    所以,在大部分情况下,尤其是使用Java语言的情况下,更建议使用对象适配器

    2.2.对象适配器

    相对于类适配器,对象适配器能提供更高的灵活性和更低的耦合度,原理上也比较简单,就是将继承修改为组合,也就是这样。
    在这里插入图片描述

    将上面的适配器代码做一下修改,如下:

    public class RecordAdapter2 implements RecordService {
    
        private NewRecordService newRecordService;
    
        public RecordAdapter2(NewRecordService newRecordService) {
            this.newRecordService = newRecordService;
        }
    
        @Override
        public Record getRecord() {
            NewRecord newRecord = newRecordService.getNewRecord();
            Record record = new Record();
            record.setFollowContent(newRecord.getCommunicateContent());
            record.setEnclosure(newRecord.getAccessory());
            return record;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在创建对象适配器时,将被适配的对象直接传入到适配器中即可,如果是Spring的服务,这些被适配的对象还可以自动依赖注入,也很方便。
    使用对象适配器时,可以注入多个Adaptee,当目标对象需要的数据需要从多个不同的接口中查询出来再做聚合的时候,就可以使用这种方式来处理。用上面的代码来说就是,跟进记录附件地址需要从不同的接口进行查询的时候。

    3.总结

    本篇主要是将适配器模式的使用,作为一种补偿模式,不建议一开始就使用适配器模式,如果能够在设计初期尽可能的避免出现接口不兼容的情况,那么直接设计出符合需求的标准接口会更优。但毕竟没有完美的设计,当设计上存在一定的缺陷又没有资源做大重构的时候,适配器模式就派上用场了。

    对于类适配器和对象适配器,区别就是在于类适配器通过继承实现,对象适配器通过组合实现,对于Java这样的单继承语言,更建议使用对象适配器,更加灵活。

    再补充一下适配器的一些使用场景:

    • 替换依赖的系统:例如上面的那个例子
    • 接入第三方库或API:第三方的API或接口设计我们不能控制,可以用适配器将传输的报文转换成我们系统中的形式来落库。
    • 整合多个接口设计:例如一个短信服务中,对于短信发送的内容要做敏感词、黑词过滤,我们有自己的规则,不同的短信服务商也有自己的规则,可以用适配器将不同的规则整合起来,方便统一调用。
    • ……
  • 相关阅读:
    【2024】springboot校服订购系统设计与实现
    vue elementUI table表格自定义样式滚动
    onnx-modifier使用
    大数据Flink(六十四):Flink运行时架构介绍
    自动驾驶技术详解
    QT 搭建FFmpeg环境
    Java标识符和关键字
    Mac安装redis详解(附图片)
    反汇编ARM程序的技术靠谱吗?——揭秘ARM架构二进制程序的反汇编技术现状
    5、JAVA入门——变量和数据类型1
  • 原文地址:https://blog.csdn.net/qq_38249409/article/details/136188774