原文地址:https://jaune162.blog/design-pattern/adapter-pattern.html
更多精彩文章请移步:https://jaune162.blog
更多专题系列文章请移步:https://books.jaune162.blog
序言
在软件开发的世界中,我们经常会遇到一个棘手的问题:随着系统的发展与迭代,新功能的需求不断涌现,而这些新功能往往需要与旧有系统进行交互。这就带来了一个挑战——新旧系统之间由于接口不兼容、数据格式不同或是通信协议有所差异等问题,导致无法直接协同工作。如何让这些原本因接口或功能不匹配的组件能够无缝对接,共同完成任务?
面对这一问题,适配器模式(Adapter Pattern)应运而生。适配器模式是一种结构型设计模式,它通过引入一个中间层——即“适配器”——来解决两个不兼容接口之间的矛盾。这个适配器充当转换器的角色,将一种接口转换成另一种客户端期望的接口,从而实现了原有功能的新用途。
就像电源插头与插座的关系一样,虽然电源插头的形状各异,但只要使用对应的转换器,就可以在不同标准的电源插座中供电。在软件设计中,适配器模式允许已有的类或模块通过引入适配器来复用,而无需修改原有代码,大大增强了系统的灵活性和可扩展性。
定义
Convert the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
结构
想象这样一个场景,在微服务场景下,有一个接口升级,参数发生了变更。但是有其他服务在大量使用老版接口,为了不影响原有业务,需要确保老版接口依然可以正常使用。那这时候就需要一个适配层,接受老版参数,然后转为新版参数,再调用新版的业务接口处理相关的业务逻辑。
实现
@Data
public class ParamV2 {private String name;private String code;private Integer type;
}public interface ServiceV2 {void doService(ParamV2 param);
}public class ServiceV2Impl implements ServiceV2 {@Overridepublic void doService(ParamV2 param) {System.out.println("升级后的业务处理逻辑");}
}
V1版本接口适配V2版本接口
@Data
public class ParamV1 {private String name;private String code;
}public interface ServiceV1 {void doService(ParamV1 param);
}public class ServiceV2Adapter implements ServiceV1 {private final ServiceV2 serviceV2;public ServiceV2Adapter(ServiceV2 serviceV2) {this.serviceV2 = serviceV2;}@Overridepublic void doService(ParamV1 param) {ParamV2 paramV2 = new ParamV2();paramV2.setName(param.getName());paramV2.setCode(param.getCode());// 设置一个默认值paramV2.setType(1);System.out.println("旧版本业务类做参数转换");this.serviceV2.doService(paramV2);}
}
测试
public class Main {public static void main(String[] args) {ServiceV2 serviceV2 = new ServiceV2Impl();ServiceV1 serviceV1 = new ServiceV2Adapter(serviceV2);ParamV1 paramV1 = new ParamV1();paramV1.setName("张三");paramV1.setCode("zhangsan");serviceV1.doService(paramV1);}
}
输出
旧版本业务类做参数转换
升级后的业务处理逻辑
通过适配层,旧版本接口无需任何改动就可以适配新版本接口。这样可以减少大量的接口升级对业务系统的改动。
本例中仅是做参数的适配,也可以做业务接口适配,即抛开
ServiceV2
,ServiceV2
是一套新的业务逻辑。而在ServiceV2Adapter
直接调用其他业务接口实现新版业务逻辑,而不必拘泥一定要转换为ServiceV2
的参数,来调用ServiceV2
的业务接口。
适配层的主要作用是做适配,只要完成了适配任务,我认为都是没有问题的。
实际的业务实例
系统中提供了一个公共的方法来给用户发送消息。但是不同的业务发送的消息不一致,如果订单过期提醒,和快递送达提醒。为了处理这种组装消息的差异,我们可以引入适配层,在适配层将消息组装成一致的消息,然后发送给用户。
而给用户发送消息又有多种形式,如短信提醒,站内信,钉钉提醒等形式,那又需要一个适配层,将这个消息对象转换为其他业务接口可以接收的消息参数。与上面的区别在于,上面的业务是在不同的业务中,将业务数据转换为统一的消息对象。而这里是要将消息对象转换为不同的三方接口参数。
InternalMessageSender
本身就可以处理 Message
类型的参数,所以无需适配层。而SmsMessageSender
无法处理 Message
类型的参数,所以需要通过适配层做一次转换处理。
因为在系统中短信发送、钉钉消息发送类的与第三方对接的接口一般都是作为公共接口来使用的。如发送短信,如果每个需要发送短信的业务都单独写一套发送短信的对接接口,那系统中重复的代码将会很多。因此一般都是有一个公共的第三方对接接口,其他业务需要的时候通过类似于适配的功能,组装三方接口需要的参数,然后调用三方接口实现对应的业务。
以上逻辑的代码实现此处不再实现。
何时使用
- 系统需要使用现有类,但其接口与系统要求的接口不兼容。
- 需要在不改变已有类的情况下,将多个类的接口统一起来。
- 需要在一个类中封装一些复杂的接口,以便其他类可以轻松地使用它。
适配器最终解决的还是不兼容的问题,一般是处理系统重已存在接口与新业务之间的兼容性问题。
与其他设计模式的联系
参考桥接模式:https://books.jaune162.com/docs/design-pattern/patterns/structural/bridge-pattern