【设计模式】如何在业务开发中使用适配器模式?

news2024/11/15 0:12:34

文章目录

  • 前言
  • 适配器模式定义
  • 通用代码实现
  • 适用场景
  • 案例场景分析
    • 一坨坨代码实现
    • 适配器模式重构
  • 总结

前言

适配器模式(Adapter Pattern):将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

说人话:这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

比如现实生活中的例子, 就像我们提到的万能充、数据线、MAC笔记本的转换头、出国旅游买个插座等等,他们都是为了适配各种不同的口 ,做的兼容。

适配器模式定义

在这里插入图片描述

  • Target目标角色:
    该角色定义把其他类转换为何种接口, 也就是我们的期望接口, 例子中的IUserInfo接口就是目标角色。
  • Adaptee源角色:
    你想把谁转换成目标角色, 这个“谁”就是源角色, 它是已经存在的、 运行良好的类或对象, 经过适配器角色的包装, 它会成为一个崭新、 靓丽的角色。
  • Adapter适配器角色:
    适配器模式的核心角色, 其他两个角色都是已经存在的角色, 而适配器角色是需要新建立的, 它的职责非常简单: 把源角色转换为目标角色, 怎么转换? 通过继承或是类关联的方式。

通用代码实现

/**
 * 目标角色
 */
public interface Target {
    void t1();
    void t2();
    void t3();
}
/**
 * 目标角色实现类
 */
public class ConcreteTarget implements Target{

    @Override
    public void t1() {
        System.out.println("目标角色 t1 方法");
    }

    @Override
    public void t2() {
        System.out.println("目标角色 t2 方法");
    }

    @Override
    public void t3() {
        System.out.println("目标角色 t3 方法");
    }
}
/**
 * 源角色:要把源角色转换成目标角色
 */
public class Adaptee {

    public void a1(){
        System.out.println("源角色 a1 方法");
    }

    public void a2(){
        System.out.println("源角色 a2 方法");
    }

    public void a3(){
        System.out.println("源角色 a3 方法");
    }
}

基于继承的类适配器

/**
 * 适配器角色
 */
public class Adapter extends Adaptee implements Target{

    @Override
    public void t1() {
        super.a1();
    }

    @Override
    public void t2() {
        super.a2();
    }

    @Override
    public void t3() {
        super.a3();
    }
}

基于组合的对象适配器

public class AdapterCompose implements Target{

    private Adaptee adaptee;

    public AdapterCompose(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    @Override
    public void t1() {
        adaptee.a1();
    }

    @Override
    public void t2() {
        adaptee.a2();
    }

    @Override
    public void t3() {
        adaptee.a3();
    }
}

测试

public class AdapterClient {

    public static void main(String[] args) {
        // 原有的业务逻辑
        Target target = new ConcreteTarget();
        target.t1();

        // 基于继承 增加适配器业务逻辑
        Target target1 = new Adapter();
        target1.t1();

        // 基于组合 增加适配器业务逻辑
        Target target2 = new AdapterCompose(new Adaptee());
        target2.t1();
    }
}

打印结果:

目标角色 t1 方法
源角色 a1 方法
源角色 a1 方法

适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。在实际开发中,选择的依据如下:

  1. 如果 Adaptee 接口并不多,那两种实现方式都可以。

  2. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。

  3. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。

适用场景

  1. 修改已使用的接口,某个已经投产中的接口需要修改,这时候使用适配器最好。

  2. 统一多个类的接口设计,比如对于敏感词过滤,需要调用好几个第三方接口,每个接口方法名,方法参数又不一样,这时候使用适配器模式,将所有第三方的接口适配为统一的接口定义。

  3. 兼容老版本接口。

  4. 适配不同格式的数据。

案例场景分析

现在假设⼀个系统需要接收各种各样的MQ消息或者接⼝,如果⼀个个的去开发,就会耗费很⼤的成本,同时对于后期的拓展也有⼀定的难度。此时就会希望有⼀个系统可以配置⼀下就把外部的MQ接⼊进⾏,这些MQ就像上⾯提到的可能是⼀些注册开户消息、商品下单消息等等。

⽽适配器的思想⽅式也恰恰可以运⽤到这⾥,并且我想强调⼀下,适配器不只是可以适配接⼝往往还可以适配⼀些属性信息。
在这里插入图片描述

一坨坨代码实现

这⾥模拟了三个不同类型的MQ消息,⽽在消息体中都有⼀些必要的字段,⽐如;⽤户ID、时间、业务ID,但是每个MQ的字段属性并不⼀样。就像⽤户ID在不同的MQ⾥也有不同的字段:uId、userId等。
注册开户MQ

public class CreateAccount {

    private String number;      // 开户编号
    private String address;     // 开户地
    private Date accountDate;   // 开户时间
    private String desc;        // 开户描述

 	// ... get/set
}

内部订单MQ

public class OrderMq {

    private String uid;           // 用户ID
    private String sku;           // 商品
    private String orderId;       // 订单ID
    private Date createOrderTime; // 下单时间

 	// ... get/set
}

第三⽅订单MQ

public class POPOrderDelivered {

    private String uId;     // 用户ID
    private String orderId; // 订单号
    private Date orderTime; // 下单时间
    private Date sku;       // 商品
    private Date skuName;   // 商品名称
    private BigDecimal decimal; // 金额

    // ... get/set
}

Mq接收消息实现

public class CreateAccountMqService {
    
    public void onMessage(String message) {
        CreateAccount mq = JSON.parseObject(message, CreateAccount.class);
        mq.getNumber();
        mq.getAccountDate();
        // ... 处理自己的业务
    }
}

三组MQ的消息都是⼀样模拟使⽤,就不⼀⼀展示了。

适配器模式重构

统⼀的MQ消息体

public class RebateInfo {

    private String userId;  // 用户ID
    private String bizId;   // 业务ID
    private Date bizTime;   // 业务时间
    private String desc;    // 业务描述

	// ... get/set
}

MQ消息中会有多种多样的类型属性,虽然他们都有同样的值提供给使⽤⽅,但是如果都这样接⼊那么当MQ消息特别多时候就会很麻烦。
所以在这个案例中我们定义了通⽤的MQ消息体,后续把所有接⼊进来的消息进⾏统⼀的处理。
MQ消息体适配类

public class MQAdapter {

    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }

    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }
}

主要⽤于把不同类型MQ种的各种属性,映射成我们需要的属性并返回。就像⼀个属性中有 ⽤户ID;uId ,映射到我们需要的 userId ,做统⼀处理。

⽽在这个处理过程中需要把映射管理传递给 Map<String, String> link ,也就是准确的描述了,当前MQ中某个属性名称,映射为我们的某个属性名称。

最终因为我们接收到的 mq 消息基本都是 json 格式,可以转换为MAP结构。最后使⽤反射调⽤的⽅式给我们的类型赋值。

在实际业务开发中,除了反射的使用外,还可以加入代理类把映射的配置交给它。这样就可以不需要每一个mq都手动创建类了。
测试类

public class ApiTest {

    @Test
    public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ParseException {
        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = s.parse("2023-02-27 20:20:16");

        CreateAccount createAccount = new CreateAccount();
        createAccount.setNumber("1000");
        createAccount.setAddress("北京");
        createAccount.setAccountDate(parse);
        createAccount.setDesc("在校开户");

        HashMap<String, String> link01 = new HashMap<String, String>();
        link01.put("userId", "number");
        link01.put("bizId", "number");
        link01.put("bizTime", "accountDate");
        link01.put("desc", "desc");
        RebateInfo rebateInfo01 = MQAdapter.filter(createAccount.toString(), link01);
        System.out.println("mq.createAccount(适配前)" + createAccount.toString());
        System.out.println("mq.createAccount(适配后)" + JSON.toJSONString(rebateInfo01));
    }
}
mq.createAccount(适配前){"accountDate":1677500416000,"address":"北京","desc":"在校开户","number":"1000"}
mq.createAccount(适配后){"bizId":"1000","bizTime":1591077840669,"desc":"在校开户","userId":"1000"}

模拟传⼊不同的MQ消息,并设置字段的映射关系。等真的业务场景开发中,就可以配这种映射配置关系交给配置⽂件或者数据库后台配置,减少编码。

总结

  1. 将目标类和适配者类解耦,通过使用适配器让不兼容的接口变成了兼容,让客户从实现的接口解耦。

  2. 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。

  3. 灵活性和扩展性都非常好在不修改原有代码的基础上增加新的适配器类,符合“开闭原则”。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Can‘‘t connect to MySQL server on localhost (10061)解决方法

首先检查MySQL 服务没有启动》如果没有启动&#xff0c;则要启动这个服务。 有时候安装mysql后使用mysql命令时报错 Cant connect to MySQL server on localhost (10061)&#xff0c;或者用net start mysql 时报服务名无效&#xff0c;一般是因为mysql服务没有启动。 打开 powe…

itop-3568开发板驱动学习笔记(18)tasklet 机制

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录tasklet 简介tasklet 结构体tasklet 初始化使能 tasklet失能 tasklettasklet 调度函数tasklet 取消调度函数tasklet 实验tasklet 简介 Tasklets 机制是linux中断处理机制中的软中断延迟机制。在linux中存在着…

【ES】搜索结果处理RestClient查询文档

【ES】搜索结果处理&RestClient查询文档2.搜索结果处理2.1.排序2.1.1.普通字段排序2.1.2.地理坐标排序2.2.分页2.2.1.基本的分页2.2.2.深度分页问题2.2.3.小结2.3.高亮2.3.1.高亮原理2.3.2.实现高亮2.4.总结3.RestClient查询文档3.1.快速入门3.1.1.发起查询请求3.1.2.解析响…

【计算机图形学】图形变换(平移变换、比例变换、旋转变换、对称变换、错切变换、复合变换)

一 实验目的 编写图形各种变换的算法二 实验内容 1&#xff1a;自行设计基本图案&#xff0c;完成1-5种简单变换 实验结果如下图所示&#xff1a; 图形初始化&#xff1a; 第一次点击左键&#xff0c;实现平移变换&#xff1a; 第二次点击左键&#xff0c;实现比例变换&…

Android 性能优化——APP启动优化详解

1.1 为什么要启动优化&#xff1f; 用户希望应用能够及时响应并快速加载&#xff0c;启动时间过长的应用不能满足这个期望&#xff0c;并且可能使用户失望。 启动太慢的结果&#xff1a; 体验效果差用户放弃使用你的应用时间越长用户流失越高产品死掉 1.2 启动优化流程及分…

HDLBits-Modules 题解【Verilog模块例化】(中文翻译+英文原文,可顺带学习英文)

Moudule 概念介绍 到目前为止&#xff0c;你已经熟悉了一个模块&#xff0c;它是一个通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是通过将较小的模块和其他连接在一起的部分&#xff08;例如赋值语句和always块&#xff09;组合而成的更大模块来构建的。因为模…

从零开始学OpenCV——图像灰度变换详解(线性与非线性变换)

文章目录图像灰度变化灰度变换介绍灰度线性变换灰度分段线性变换图像点运算灰度非线性变换线性点运算灰度的非线性变换&#xff1a;对数变换灰度的非线性变换&#xff1a;伽马变换灰度的非线性变换&#xff1a;对比拉伸灰度的非线性变换&#xff1a; S形灰度变换灰度的非线性变…

tomcat中出现RFC7230和RFC3986问题解析

问题截图 问题分析 出现上述问题&#xff0c;是因为各版本tomcat中对特殊字符和请求路径中携带中文参数而产生的错误提示。 解决办法 1、调整tomcat版本 tomcat 7.0.76之前的版本不会出现类似问题 2、tomcat9之前&#xff0c;修改tomcat目录底下的/conf/catalina.properti…

233:vue+openlayers绘制渐变填充色的圆形、多边形

第233个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayer中绘制带有渐变填充色的圆形、多边形。这里用canvas的方式去渲染,用到了DEVICE_PIXEL_RATIO,设备上的物理像素与设备无关像素 (dips) 之间的比率 (window.devicePixelRatio)。 直接复制下面的 vue+openlayer…

用ChatGPT创建一个REST API

ChatGPT是OpenAI公司开发的大型语言模型。在本文中&#xff0c;主要探讨如何使用ChatGPT在C#中创建REST API。 一、简介 ChatGPT是由人工智能研究中心OpenAI创建的尖端自然语言处理模型&#xff0c;OpenAI公司是由埃隆马斯克、萨姆奥特曼和格雷格布罗克曼共同创办的。该模型于…

360浏览器+Adobe Acrobat DC实现在线预览PDF大样校对

甲方&#xff1a;实现方正PDF文字大样校对&#xff0c;校对后在360浏览器中新开一个页面在线预览PDF文字大样校对结果。 我方实现过程&#xff1a; 1.方案选择 方案零&#xff1a;使用浏览器自带的PDF阅览器&#xff0c;经测试360极速模型,谷歌等软件能预览但是标记的PDF内容…

【笔记】大模型,大资料

大模型&#xff0c;大资料&#xff0c;loss会降低&#xff0c;准确率会增加 1大模型 1.1模型的顿悟时刻 举了一个一知半解的例子 1.2 模型 chain of thought 模型足够大时才会有比较好的作用 calibration 检测模型对于答案的confidence 会出现 “u-shape” 2.大资料 文法…

系统复杂度之【高可用】

接着&#xff0c;我们聊聊复杂度的第二个要求高可用。 参考维基百科&#xff0c;先来看看高可用的定义。 系统无中断地执行其功能的能力&#xff0c;代表系统的可用性程度&#xff0c;是进行系统设计时的准则之一。 这个定义的关键在于“ 无中断”&#xff0c;但恰好难点也在“…

Java 并发编程面试题——Future

目录1.什么是 Future 模式&#xff1f;Java 中是如何实现的&#xff1f;2.Callable、Future 与 FutureTask 分别是什么&#xff1f;2.1.Callable 接口2.2.Future 接口2.3.FutureTask 类3.CompletableFuture 类有什么用&#xff1f;1.什么是 Future 模式&#xff1f;Java 中是如…

windows系统管理_Windows server 2016 组管理与授权

组账户的概述 在 windows 服务器中&#xff0c;当我们需要为多个用户设置相同的权限时&#xff0c;一个一个的逐一设置会比较 麻烦&#xff0c;这个时候我们就需要用到另一种模式&#xff0c;组账户&#xff0c;使用此账户来进行简化操作。 在以后的职场中&#xff0c;每家公司…

Flink 优化 (五) --------- Job 优化

目录一、使用 DataGen 造数据1. DataStream 的 DataGenerator2. SQL 的 DataGenerator二、算子指定 UUID三、链路延迟测量四、开启对象重用五、细粒度滑动窗口优化一、使用 DataGen 造数据 开发完 Flink 作业&#xff0c;压测的方式很简单&#xff0c;先在 kafka 中积压数据&a…

全景图像畸变校正

1.简介 理想的相机基本上是小孔成像的&#xff0c;在小孔成像模型中&#xff0c;如果焦距一定&#xff0c;那么图像传感器像素平面的面积直接决定了相机视场角的大小&#xff0c;超过这个视场角范围的物体不会被镜头获取到。因此基于透镜成像原理的相机&#xff0c;视场角无法…

JAVA解析XML时的内存消耗问题

问题出现 最近一个项目中&#xff0c;有个需求功能是从外部传入的XML读取数据&#xff0c;然后写入到数据库中。 写完之后&#xff0c;有在本地电脑上的Tomcat跑了一下&#xff0c;正常读取到XML中的数据&#xff0c;并整理好后&#xff0c;插入了数据库保存了。但是线上运行的…

C语言文件操作复习回顾(2)

TIPS 文件的顺序读写&#xff1a;fgetc, fputc, fputs&#xff08;一行字符串的输出\n注意一下&#xff09;, fgets&#xff08;一行字符串的输入\n三特性&#xff09;&#xff0c;fprintf&#xff08;格式化字符串的输出联想printf很简单&#xff09;&#xff0c;fscanf&…

pyspark 实验二,rdd编程

1.环境准备 start-all.sh启动Hadoop ./bin start-all.sh 启动spark 上传数据集 1.求该系总共多少学生 linessc.textFile("file:///home/data.txt") res lines.map(lambda x:x.split(",")).map(lambda x:x[0]) sumres.distinct() sum.cont() 2.求该系设置…