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

文章目录

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

前言

适配器模式(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. 灵活性和扩展性都非常好在不修改原有代码的基础上增加新的适配器类,符合“开闭原则”。

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

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

相关文章

高速Serdes技术(FPGA领域应用)

目录引入一、Serdes&#xff08;概念-历程&#xff09;1、概念2、技术现状3、发展历程二、Serdes结构三、在FPGA领域中的运用四、Serdes跟Lvds的关系五、Xilinx 有关 serdes的文档六、参考文献引入 回顾接口技术发展历史&#xff0c;其实数据的传输最开始是低速的串行接口&…

OSI七层网络模型与TCP/IP四层模型

一、OSI七层网络模型 OSI 七层模型 是国际标准化组织提出一个网络分层模型&#xff0c;其大体结构以及每一层提供的功能如下图所示&#xff1a; 但由于各方面原因&#xff0c;OSI 七层模型并没有被广泛应用&#xff0c;更多的是作为网络分层的一种基础理论模型。 二、TCP/IP…

NumPy 基础知识 :1~5

原文&#xff1a;Numpy Essentials 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 一、NumPy 简介 “我宁愿使用通用语言进行数学运算&#xff0c;也不愿尝试使用数学语言进行通用编程。” – John D Cook 在过去的十年中&#xff0c;Python 已成为科学计算中最受欢迎…

MVCC

MVCC基本概念 当前读 当前读 : 读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁. 对于我们日常的操作. 如 : select....lock in share mode(共享锁) , select * for update , update ,insert,delete(排他锁) 都是一种当前读. 快…

Java对象模型

介绍 Java是一种面向对象的语言&#xff0c;而Java对象在JVM中存储是由一定结构的。而这个 Java对象自身的存储模型称之为Java对象模型HotSpot虚拟机中&#xff0c;设计了一个OOP-Klass Model.OOP指的是普通对象指针&#xff0c;而Klass用来描述对象的具体类型。如下图所示是一…

文章生成器写出来的原创文章

文章生成机器人 文章生成机器人是一种基于人工智能技术和自然语言处理算法的程序&#xff0c;可以自动地生成高质量、原创的文章。 文章生成机器人的优点如下&#xff1a; 提高工作效率&#xff1a;文章生成机器人能够在较短的时间内自动帮助用户生成大量的文章&#xff0c;提…

Python 小型项目大全 21~25

二十一、DNA 可视化 原文&#xff1a;http://inventwithpython.com/bigbookpython/project21.html 脱氧核糖核酸是一种微小的分子&#xff0c;存在于我们身体的每个细胞中&#xff0c;包含着我们身体如何生长的蓝图。它看起来像一对核苷酸分子的双螺旋结构&#xff1a;鸟嘌呤、…

计算机网络微课堂1-3节

目录 1. TCP/TP协议​编辑 2. 3.调制解调器 4.因特网的组成 5.电路交换 6.分组交换 重要常用 7.报文交换 8.总结电路交换 报文交换和分组交换 9. 1. TCP/TP协议 2. ISP 网络提供商 ISP的三层 国际 国家 和本地 3.调制解调器 什么是调制解调器&#xff0c;它存在的…

Python 小型项目大全 11~15

十一、标题党生成器 原文&#xff1a;http://inventwithpython.com/bigbookpython/project11.html 我们的网站需要欺骗人们去看广告&#xff01;但是想出有创意的原创内容太难了。幸运的是&#xff0c;有了标题党生成器&#xff0c;我们可以让一台计算机产生数百万个令人发指的…

【Linux】浅析Input子系统

文章目录1. 框架1.1 数据结构1.2 evdev_handler1.3 evdev_init1.4 input_register_handler2. 应用如何打开节点并读取到事件数据2.1 evdev_fops2.2 evdev_open2.3 evdev_release2.4 evdev_read2.5 evdev_write2.6 evdev_poll2.7 evdev_fasync2.8 evdev_ioctl2.9 evdev_ioctl_co…

[考研数据结构]第3章之栈的基本知识与操作

文章目录 栈的基本概念 栈的实现 顺序栈 共享栈 链栈 栈的基本概念 栈的定义 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或删除操作的线性表 相关术语 栈顶&#xff08;Top&#xff09;线性表允许进行插入或删除的那一端称之为栈顶栈底&#xff08;Bottom&…

【计算机网络-数据链路层】集线器、网桥、交换机

本文许多文字和图片使用了湖科大教书匠&#xff08;高军老师&#xff09;的 PPT&#xff0c;在此表示感谢。正是他让非科班的我能以奇妙的方式走进网络的世界。 文章目录1 【物理层】集线器&#xff08;Hub&#xff09;——共享式以太网1.1 为什么使用集线器&#xff1f;1.2 集…

macOS Monterey 12.6.5 (21G531) Boot ISO 原版可引导镜像

本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Windows 和 Linux 中创建可引导介质。 2023 年 4 月 10 日&#xff08;北京…

ESXI 6.7全面系统教程~汇总

ESXI 6.7全面系统教程 许可证&#xff1a;0A65P-00HD0-375M1-M097M-22P7H esxi 是一个脱机系统&#xff0c;也是一个虚拟机系统与vmware 相比&#xff0c;它可以直接运行在硬件上&#xff0c;这样可以减少资源浪费&#xff0c;一般用于服务器上&#xff1b;下面是esxi 的完整…

stable-diffusion-webui-colab部署记录

stable-diffusion-webui-colab 该模型可以在网上云端部署stable-diffusion&#xff0c;减少本地部署的繁琐步骤降低配置要求的依赖。 一、进入stable-diffusion-webui-colab 1.网址&#xff1a;https://github.com/camenduru/stable-diffusion-webui-colab 在分支中选择driv…

我的创作纪念日:Unity CEO表示生成式AI将是Unity近期发展重点,发布神秘影片预告

PICK 未来的AI技术将会让人类迎来下一个生产力变革&#xff0c;这其中也包括生成型AI的突破性革新。各大公司也正在竞相推出AIGC工具&#xff0c;其中微软的Copilot、Adobe的Firefly、Github的chatGPT等引起了人们的关注。然而&#xff0c;游戏开发领域似乎还没有一款真正针对性…

Vulnhub:Digitalworld.local (Development)靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.130 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --scripthttp-enum 192.168.111.130 查看网站首页源码 访问development目录&#xff0c;提示存在一个流量包 查看流量包发现另一个网站路径&#xff1a;/devel…

java继承类怎么写

继承类是通过把父类的方法和属性继承到一个类中&#xff0c;而子类的方法和属性是子类自己定义的。 Java中有一个很重要的概念叫做继承&#xff0c;这也是 Java语言的精髓所在。Java语言提供了一种机制&#xff0c;叫做派生类。在 Java中&#xff0c;如果没有实现了某个派生类方…

python 调用c++

python中调用c&#xff0c;函数参数用 int类型&#xff0c;返回值为类型1,且返回值为 false。 注意&#xff1a;如果你使用了C中的 false&#xff0c;则返回的是-1。 在 Python中调用C时&#xff0c;你会得到一个名为 bool的类&#xff0c;其中包含了两个成员变量&#xff1a; …

多智能体深度强化学习在移动边缘计算的联合多通道访问和任务卸载中的应用

多智能体深度强化学习在移动边缘计算的联合多通道访问和任务卸载中的应用主要贡献与相关工作比较的贡献三、系统模型&#xff08;only 2 pages&#xff09;3.1 网络模型3.2 通信模型3.3 计算模型3.3.1 本地计算3.3.2 卸载计算四、预备知识&#xff08;only 1 page&#xff09;五…