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

文章目录

  • 1.概述
  • 2.两种适配器模式
    • 2.1.类适配器
    • 2.2.对象适配器
  • 3.总结

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;
        }
    }
    
  • 新系统中的定义
/**
 * 新系统沟通记录对象
 */
@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;
    }
}

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

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

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;
    }
}

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

public class RecordAdapterTest {
    @Test
    public void testGetRecord() {
        RecordService recordService = new RecordServiceImpl();
        System.out.println(recordService.getRecord());
        recordService = new RecordAdapter();
        System.out.println(recordService.getRecord());
    }
}

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;
    }
}

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

3.总结

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

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

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

  • 替换依赖的系统:例如上面的那个例子
  • 接入第三方库或API:第三方的API或接口设计我们不能控制,可以用适配器将传输的报文转换成我们系统中的形式来落库。
  • 整合多个接口设计:例如一个短信服务中,对于短信发送的内容要做敏感词、黑词过滤,我们有自己的规则,不同的短信服务商也有自己的规则,可以用适配器将不同的规则整合起来,方便统一调用。
  • ……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/400923.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Sora横空出世!AI将如何撬动未来?

近日,OpenAI 发布首个视频生成“Sora”模型,该模型通过接收文字指令,即可生成60秒的短视频。 而在2022年末,同样是OpenAI发布的AI语言模型ChatGPT,简化了文本撰写、创意构思以及代码校验等任务。用户仅需输入一个指令&…

1.30数据包络法

数据包络分析法(Data Envelopment Analysis,DEA)是一种非参数的效率评价方法,用于衡量相对效率和评估多个决策单元(DMU)的绩效。 在数据包络分析中,每个决策单元都是一个输入和输出数据向量的组…

[Angular 基础] - service 服务

[Angular 基础] - service 服务 之前的笔记就列举三个好了……没想到 Angular 东西这么多(ー ー;)……全加感觉越来越凑字数了 [Angular 基础] - 视图封装 & 局部引用 & 父子组件中内容传递 [Angular 基础] - 生命周期函数 [Angular 基础] - 自…

Android T 远程动画显示流程其二——动画的添加流程(更新中)

前言 接着上篇文章分析 Android T 远程动画显示流程其一 切入点——处理应用的显示过渡 下面,我们以从桌面点击一个应用启动的场景来分析远程动画的流程,窗口添加的流程见Android T WMS窗口相关流程 这里我们从AppTransitionController.handleAppTran…

量子加密机的工作原理是什么

量子加密机,作为现代加密技术的一大飞跃,正逐渐成为信息安全领域的研究热点。与传统的加密方法相比,量子加密技术以其独特的优势,为信息安全提供了更为坚实的保障。 量子加密的核心在于利用量子力学的特性,尤其是量子纠…

sizeof()的易错点

你也可以传入一个变量的名字(而不只是类型)给 sizeof(),但在一些情况下,可能得不到你要的结果,所以要小心使用。例如,看看下面的代码片段: 在第一行,我们为 10 个整数的数组声明了空…

LInux-信号1

文章目录 前言一、信号是什么?二、学习步骤使用kill -l命令查看信号列表可以看到有那么多信号,那么进程是如何识别这么多信号的呢? 使用kill命令终止进程信号的捕捉kill函数raise函数abort函数 Core dump如何查看自己的核心转储功能是否被打开…

C++运算符重载、迭代器、继承、派生类的构造与析构、重载、隐藏和覆盖

运算符重载 普通的C运算符重载成员方法基本都知道&#xff0c;如果没有定义成员方法&#xff0c;那么编译器会优先寻找全局重载运算符看看是否匹配。这里给出了一个使用友元函数和全局重载运算符来实现整数与复数类对象的相加运算。 #include <iostream>class Complex …

Flink双流(join)

一、介绍 Join大体分类只有两种&#xff1a;Window Join和Interval Join Window Join有可以根据Window的类型细分出3种&#xff1a;Tumbling(滚动) Window Join、Sliding(滑动) Window Join、Session(会话) Widnow Join。 &#x1f338;Window 类型的join都是利用window的机制…

蜂鸣器实验

1.有源蜂鸣器简介 蜂鸣器常用于计算机、打印机、报警器、电子玩具等电子产品中&#xff0c;常用的蜂鸣器有两种&#xff1a; 有源蜂鸣器和无源蜂鸣器&#xff0c;这里的有“源”不是电源&#xff0c;而是震荡源&#xff0c;有源蜂鸣器内部带有震荡 源&#xff0c;所以有源蜂…

OD(8)之Mermaid流程图(flowcharts)使用详解

OD(8)之Mermaid流程图(flowcharts)使用详解 Author: Once Day Date: 2024年2月20日 漫漫长路才刚刚开始… 全系列文章可参考专栏: Linux实践记录_Once_day的博客-CSDN博客 参考文章: 关于 Mermaid | Mermaid 中文网 (nodejs.cn)Mermaid | Diagramming and charting tool‍…

Maven 私服 Nexus3

一、Maven和Nexus3 简介 Maven是一个采用纯Java编写的开源项目管理工具&#xff0c;采用一种被称之为Project Object Model(POM)概念来管理项目&#xff0c;所有的项目配置信息都被定义在一个叫做POM.xml的文件中, 通过该文件Maven可以管理项目的整个生命周期&#xff0c;包括…

maven 打包命令

Maven是基于项目对象模型(POM project object model)&#xff0c;可以通过一小段描述信息&#xff08;配置&#xff09;来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 Maven的核心功能便是合理叙述项目间的依赖关系&#xff0c;通俗点讲&#xff0c;就是通过po…

06 分频器设计

分频器简介 实现分频一般有两种方法&#xff0c;一种方法是直接使用 PLL 进行分频&#xff0c;比如在 FPGA 或者 ASIC 设计中&#xff0c;都可以直接使用 PLL 进行分频。但是这种分频有时候受限于 PLL 本身的特性&#xff0c;无法得到频率很低的时钟信号&#xff0c;比如输入 …

Predis Multi-Zone

A Data Flow Framework with High Throughput and Low Latency for Permissioned Blockchains 联盟链的吞吐瓶颈由共识层和网络层的数据分发过程共同决定。 Predis 协议利用了共识节点的空闲带宽&#xff0c;提前分发区块中的内容即bundle&#xff0c;减少了共识区块中的内容&…

2024龙年特别篇 -- 魔法指针 之 assert断言 传址调用 传值调用

你是否为 assert断言&#xff0c;传址调用&#xff0c;传值调用而进一步加深印象&#xff0c;接下来就让白子寰同学为你详细讲解&#xff01;&#xff01;&#xff01; 目录 assert断言 概念 assert介绍 #define NDEBUG的使用 注意事项 传值调用 VS 传址调用 传值调用…

C语言推荐书籍

本书详细讲解了C语言的基本概念和编程技巧。全书共17章。第1章、第2章介绍了C语言编程的预备知识。第3章&#xff5e;第15章详细讲解了C语言的相关知识&#xff0c;包括数据类型、格式化输入/输出、运算符、表达式、语句、循环、字符输入和输出、函数、数组和指针、字符和字符串…

CS_Smb_Beacon上线不出网机器

当我们想上线不出网的机器的时候&#xff0c;我们可以通过上传工具来实现&#xff0c;但是有没有不用上传工具的方法呢&#xff1f;&#xff1f;&#xff1f; 有&#xff01;&#xff01;&#xff01; 而且cs会自带&#xff01;&#xff01;&#xff01; 1.基础的网络拓扑 以下…

MYSQL数据库详解

一、数据库的基本概念 数据&#xff08;data&#xff09;&#xff1a;指对客观事物进行描述并可以鉴别的符号。这些符号是可识别的&#xff0c;抽象的。 比如数字、图片、音频等。 数据库管理系统&#xff08;DBMS&#xff09;&#xff1a;数据库极其管理它的软件组成。 数据库…

RocketMQ-架构与设计

RocketMQ架构与设计 一、简介二、框架概述1.设计特点 三、架构图1.Producer2.Consumer3.NameServer4.BrokerServer 四、基本特性1.消息顺序性1.1 全局顺序1.2 分区顺序 2.消息回溯3.消息重投4.消息重试5.延迟队列&#xff08;定时消息&#xff09;6.重试队列7.死信队列8.消息语…