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"
}
}
这个时候问题就出现了,sendMessage
和 simpleSendMessage
方法的入参不一致,导致调用接口编译不通过,报错信息为入参类型不匹配。这种场景使用 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 委托适配器
使用类适配器大家可以自行网上搜索实现方式
两者的区别就是 : 使用类适配器时,如果 Adaptee
和 Target
中存在的方法不需要进行适配,在 Adapter
就可以不实现这类方法,通过继承可以引用到父类的方法
6.1 适配器的选择
针对这两种适配器的方式,根据不同的场景选择哪一种更为合适呢?
如果 Adaptee 接口比较少,那么两种方式实现没有什么区别
如果 Target 中接口很多,并且和 Adaptee 中定义的接口语义大部分保持一致,那么这种情况推荐使用类适配器,可以保持更少的代码
因为在 Adaptee 和 Target 中如果方法名称一致、实现语义一致,那么 Adapter 中可以直接复用父类 Adaptee 的方法实现
如果 Adaptee 和 Target 中的方法实现大部分不一致,推荐使用委托适配的方式
因为从设计原则上而言,组合在灵活性上比继承更有优势
07、示例为什么用委托适配器?
文中代码例子使用的SpringBoot框架。大家可以设想一下,如果使用了类委托器的方式有什么问题?不清楚的可以公众号留言沟通
08、总结
看再多的文章不如在项目中实际运用一次,网上很多文章中的内容讲的比较抽象,听的云里雾里
委托适配器核心思想就是增加一个接口(Target),里面写上需要适配的方法,建立一个适配类(Adapter)实现新加的接口,然后依赖原接口(Adaptee)
最后总结一句话:纸上得来终觉浅,绝知此事要躬行