程序员内功心法之适配器模式

简介: 程序员内功心法之适配器模式

在这里插入图片描述

01、什么是Adapter模式?

适配器模式的英文翻译是 Adapter Design Pattern,是一种 结构型模式。根据名字可以看出,这个模式就是用来做适配的,它能够将不兼容的接口转换为兼容的接口

Adapter 可以认为是一种 补救模式,原因在于原接口不能满足客户端使用,所以需要一个转换层来进行适配

如果能够在设计之初就能考虑到逻辑上的变数,可以极大程度上避免使用 Adapter

1.1 模式中的角色

目标接口 Target : 客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口

原接口 Adaptee : 需要适配的类或适配者类

适配器 Adapter : 通过包装一个需要适配的对象,把原接口转换成目标接口,或者说将原接口适配目标接口

1.2 举例说明

举一个工作中的例子,我用的是18款的 macbook pro,虽然有四个 Type-C 接口,但是却无法插入 USB 数据线,这个时候就需要买个 扩展坞(转换器) 来做这个工作

结合上面的角色:USB接口就是 Target,而电脑的Type-C则是 Adaptee,扩展坞就是 Adapter

02、Adapter模式应用场景

应用场景根据业务是灵活多变的,举两个常见的例子:

  • 统一多个相同语义的接口,抽象为统一的接口
  • 帮助接口不兼容而无法一起工作的类实现功能

03、Adapter模式的两种实现

分别是类适配器和委托适配器(对象适配器)

类适配器就是通过继承的方式来实现适配,委托适配器使用组合的方式来实现

04、实际场景

代码这一块不喜欢用Demo来实现。在学习设计模式初期以及理解上面不够友好,所以后面关于设计模式系列的文章,会使用项目中的实际案例或者分析框架中的源码

4.1 消息通知系统

最近在做一个消息通知的系统,主要功能是将钉钉开放平台、企业微信开放平台、手机短信、邮箱通知等平台消息发送进行接入

通过将各个平台的消息参数进行聚合,可以更方便的使用消息发送这个功能。遵从 接口命名不对外暴露任何实现细节,以及 封装具体的实现细节,所以设计上将发送各种类型消息定义为一个接口 SendMessageService,方法定义为sendMessage,入参类型 SendMessageReq

在设想中这种设计方式是没有任何问题的。由于各平台的消息种类比较多(比如:钉钉机器人消息类型就包括Text、Markdown、Link、ActionCard等六种类型),以及不同平台的参数命名和语义不一致

导致虽然通过一个接口可以发送不同平台不同类型的消息,但是入参中的层级关系较深

{
    "sendType": "dingtalk",
    "dingMessage": {
        "msgtype": "markdown",
        "token": "xxxx",
        "messageMd": {
            "messageTitle": "监控报警",
            "contentTitle": "[报警]应用运行中发生异常",
            "atMobiles": [
                "xxxx"
            ],
            "content": {
                "xxxx" : "xxxx"
            }
        }
    }
}

为了解决这种使用上的痛点,所以推出了 simpleSendMessage 这个方法,寓意以一种简单的方式发送消息,入参类型 SimpleMessageReq,不过在发送消息时依然调用 sendMessage 方法

{
    "token": "xxxx",
    "sendTo": [
        "xxxx"
    ],
    "content": {
        "xxxx" : "xxxx"
    }
}

这个时候问题就出现了,sendMessagesimpleSendMessage 方法的入参不一致,导致调用接口编译不通过,报错信息为入参类型不匹配。这种场景使用 Adapter 模式再合适不过了

下面的例子使用 委托适配器 来实现接口整合

4.2 委托适配器类图

因为图片大小原因,参数类型进行了缩写

05、代码示例

5.1 最初的消息发送

SendMessageController

@Autowired
private SendMessageService sendMessageService;

@PostMapping("/message/send")
@ApiOperation(value = "发送消息", notes = "通过此接口可以发送各平台类型的消息")
public Result<Void> sendMessage(
            @RequestBody SendMessageReqDTO sendMessageReqDTO) {
    sendMessageService.sendMessage(sendMessageReqDTO);
    return Results.success();
}

SendMessageService

public interface SendMessageService {

    /**
     * 发送消息
     *
     * @param messageRequest
     */
    void sendMessage(SendMessageRequest messageRequest);
}

SendMessageServiceImpl

@Service
public class SendMessageServiceImpl implements SendMessageService {

    @Override
    public void sendMessage(SendMessageRequest messageRequest) {
        // xxxxxx
    }
}

5.2 加入了简单消息发送

开发简单的方式发送消息,定义 SimpleSendMessageReqDTO 入参,这时调用统一的发送消息接口就不支持了,使用委托适配器模式

Controller(Client)

@Autowired
private SimpleSendMessageService simpleSendMessageService;

@PostMapping("/simple/message/send")
@ApiOperation(value = "简单方式发送消息", notes = "消息发送类型较为简单, 支持发送各平台")
public Result<Void> sendMessage(
            @RequestBody SimpleSendMessageReqDTO simpleSendMessageReqDTO) {
    simpleSendMessageService.simpleSendMessage(simpleSendMessageReqDTO);
    return Results.success();
}

SimpleSendMessageService(Target)

public interface SimpleSendMessageService {

    /**
     * 发送简单消息
     *
     * @param simpleSendMessageReqDTO
     */
    void simpleSendMessage(SimpleSendMessageReqDTO simpleSendMessageReqDTO);
}

SendMessageServiceAdapter(Adapter)

@Service
public class SendMessageServiceAdapter implements SimpleSendMessageService {

    @Autowired
    private SendMessageService sendMessageService;

    @Override
    public void simpleSendMessage(SimpleSendMessageReqDTO simpleSendMessageReqDTO) {
        // 对参数类型进行适配
        SendMessageRequest sendMessageRequest = buildMessageRequest(simpleSendMessageReqDTO);
        sendMessageService.sendMessage(sendMessageRequest);
    }
}

SendMessageService(Adoptee)

public interface SendMessageService {

    /**
     * 发送消息
     *
     * @param messageRequest
     */
    void sendMessage(SendMessageRequest messageRequest);
}

至此,一个完整的委托适配器就定义完成了

06、类适配器 OR 委托适配器

使用类适配器大家可以自行网上搜索实现方式

两者的区别就是 : 使用类适配器时,如果 AdapteeTarget中存在的方法不需要进行适配,在 Adapter 就可以不实现这类方法,通过继承可以引用到父类的方法

6.1 适配器的选择

针对这两种适配器的方式,根据不同的场景选择哪一种更为合适呢?

如果 Adaptee 接口比较少,那么两种方式实现没有什么区别

如果 Target 中接口很多,并且和 Adaptee 中定义的接口语义大部分保持一致,那么这种情况推荐使用类适配器,可以保持更少的代码

因为在 Adaptee 和 Target 中如果方法名称一致、实现语义一致,那么 Adapter 中可以直接复用父类 Adaptee 的方法实现

如果 Adaptee 和 Target 中的方法实现大部分不一致,推荐使用委托适配的方式

因为从设计原则上而言,组合在灵活性上比继承更有优势

07、示例为什么用委托适配器?

文中代码例子使用的SpringBoot框架。大家可以设想一下,如果使用了类委托器的方式有什么问题?不清楚的可以公众号留言沟通

08、总结

看再多的文章不如在项目中实际运用一次,网上很多文章中的内容讲的比较抽象,听的云里雾里

委托适配器核心思想就是增加一个接口(Target),里面写上需要适配的方法,建立一个适配类(Adapter)实现新加的接口,然后依赖原接口(Adaptee)

最后总结一句话:纸上得来终觉浅,绝知此事要躬行

相关文章
|
缓存 NoSQL 应用服务中间件
万字攻略,社招腾讯天美C++后台面经,面试题整理(上)
万字攻略,社招腾讯天美C++后台面经,面试题整理
|
Java Maven
【开源视频联动物联网平台】J2mod库写一个Modbus RTU 服务器
【开源视频联动物联网平台】J2mod库写一个Modbus RTU 服务器
971 0
|
存储 安全 API
【嵌入式系统】DMA工作原理与常用函数解析
【嵌入式系统】DMA工作原理与常用函数解析
1431 0
【嵌入式系统】DMA工作原理与常用函数解析
|
XML C# 数据格式
WPF技术之xmlns
在WPF中,xmlns是XML命名空间(XML Namespace)的缩写,用于引入不同的XML命名空间,以便在XAML文件中使用特定的命名空间中的元素、属性和类型。
811 1
|
监控 安全 芯片
看见“信任”,可信计算史上最全解析
等保2.0将可信提升到一个新的强度。在等保一到四级都有可信的要求,主要在三个领域:计算环境可信、网络可信、接入可信。
34919 1
看见“信任”,可信计算史上最全解析
|
12月前
|
缓存 算法 Oracle
深度干货 如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
数据库高可用(High Availability,HA)是指在系统遇到故障或异常情况时,能够自动快速地恢复并保持服务可用性的能力。如果数据库只有一个实例,该实例所在的服务器一旦发生故障,那就很难在短时间内恢复服务。长时间的服务中断会造成很大的损失,因此数据库高可用一般通过多实例副本冗余实现,如果一个实例发生故障,则可以将业务转移到另一个实例,快速恢复服务。
深度干货  如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
|
C语言 索引
C语言编译环境中的 调试功能及常见错误提示
这篇文章介绍了C语言编译环境中的调试功能,包括快捷键操作、块操作、查找替换等,并详细分析了编译中常见的错误类型及其解决方法,同时提供了常见错误信息的索引供参考。
|
测试技术 Python
Python中的异步编程与`asyncio`库
Python中的异步编程与`asyncio`库
|
JavaScript 前端开发 网络协议
抖音直播弹幕数据逆向:websocket和JS注入
抖音直播弹幕数据逆向:websocket和JS注入
1785 1

热门文章

最新文章