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

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

在这里插入图片描述

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)

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

相关文章
|
1月前
|
存储 算法 测试技术
【软件设计师备考 专题 】软件设计方法:结构化设计与面向对象设计
【软件设计师备考 专题 】软件设计方法:结构化设计与面向对象设计
43 0
|
2月前
|
算法
【编程技巧】精通编程的秘密武器:高效编程技巧揭秘!
【编程技巧】精通编程的秘密武器:高效编程技巧揭秘!
21 0
|
4月前
|
设计模式 架构师 Java
牛皮了!世界级架构师,图解面向对象编程,小学生都能看得懂
面向对象编程(Object-oriented Programming,缩写:OOP)是软件工程中一种具有对象概念的编程范式(Programming Paradigm),同时也是一种程序开发的抽象方针,与之对应的编程范式还有:函数式编程(Functional Programming)、过程式编程(Procedural Programming)、响应式编程(Reactive Programming)等。
|
9月前
|
存储 数据管理 人机交互
【软工视频】第九章面向对象技术
【软工视频】第九章面向对象技术
|
10月前
|
算法
谈一谈|编程中的数学思维
谈一谈|编程中的数学思维
105 0
|
自然语言处理 搜索推荐 Java
重拾面向对象软件设计
软件设计的最大目标,就是降低复杂性,万物不为我所有,但万物皆为我用。引用 JDK 集合框架创办人 Josh Bloch 的一句话来结束。学习编程艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则。
10883 1
重拾面向对象软件设计
|
设计模式 数据采集 算法
还记得设计模式中称霸武林的的六大设计原则吗?
设计模式中称霸武林的的六大设计原则
110 0
还记得设计模式中称霸武林的的六大设计原则吗?
|
JavaScript 前端开发 Java
59条有趣的程序员编程箴言
  下面收集的语录涉及软件开发、代码维护、调试纠错、软件bug、系统设计、文档、代码质量、测试和软件开发团队管理等方面。虽然它们有些搞笑,但却真实无比。只有程序员才能理解这些编程语句里的真正内涵。
184 0
|
JavaScript 前端开发 机器人
梦回战国,领略两千多年前公孙龙如何将面向对象运用得炉火纯青
2200 年前的战国时期,赵国平原君的食客公孙龙有一天骑着白马进城时,被守城的官兵以马不能进城而将其拦下.公孙龙当众即兴演讲,口述&quot;白马非马&quot;一论.守城的官兵被说的一愣一愣的,无法反驳。于是公孙龙就骑着他&#39;不是马的白马&#39;大摇大摆进城去了,这其实就是历史上最为经典的一次面向对象思维的阐述
28580 11
|
算法 程序员 编译器
面向对象之【探索C++】硬核 造轮子的快乐源泉
面向对象之【探索C++】硬核 造轮子的快乐源泉
622 3

相关实验场景

更多