Adapter in coding is a design pattern, but also an architectural concept and solution

I signed up to participate in the first challenge of the Golden Stone Project - share the 100,000 prize pool, this is my 4th article, click to view the details of the event


Hello everyone, see you again.

I don't know if you have seen or used the following thing? This is a socket converter . We all know that 220valternating current is used daily, but the current and voltage used in different countries abroad are different (such as those used in Japan 110v), and the interface styles of sockets are also different (such as those used in European countries are two small Cylindrical plug interface), if we travel to other countries, with the help of this socket adapter, we can make our mobile phone chargers work normally abroad.

Of course, in addition to using socket converters, there is another way to allow us to use various electronic products normally after going abroad, that is to buy a new set locally ! Obviously, such a cost will be very huge, obviously not in line with our diligence ( nang ) frugality ( zhong ) support ( xiu ) family ( se ) characteristics.

Friends who have read my previous articles should know that my article has been repeatedly expounding one of my own concepts, that is, " coding comes from life ", and this is still no exception. Simple philosophical thinking in real life is actually reflected all the time in the code world. In the example above, isn’t this kind of situation frequently staged in our project?

We first implemented a set of code according to the original business logic, and then a new requirement came up. If we develop a new set of code, we need to invest a lot of manpower and material resources, so the first choice is to think about how to reuse the existing logic. Use existing logic to implement business docking adaptation with minimal cost.

In this article, we will use this "socket converter" as an entry point to talk about the "socket converter" that is ubiquitous in software systems - the adapter in coding ( Adapter). AdapterIt is not because Adapterof how complicated the technical implementation is Adapter, but it is actually very simple to realize, and many people are actually using it intentionally or unintentionally. More, I want to discuss this idea of ​​using Adapter to reuse and compatible with existing logic , and how to use Adapter to practice the system architecture design concept of OCP (Open-Closed Principle) .

The beauty of Adapter

Old wine in a new bottle: reuse of ready-made implementation logic

Packing old wine in a new bottle is a very “saving” operation in our system, which allows us to quickly encapsulate a new business function based on an existing capability. Of course, sometimes, the existing capability of the system may Some aspects cannot fully meet the needs of the new business, and some conversion and adaptation processing is required.

for example:

A video website originally had a commenting capability. Users could post comments below the video, and the comment content would be displayed on the page below the video in the form of a list. Now it is necessary to develop a new function to support the ability of video to send barrage and display the barrage on the video playback screen.

In terms of required functions, comments have many similarities with barrage . For the backend, the processing logic and storage data structure are almost the same, except that when the data is 列表APIrealized, it is necessary to filter out the comment information and display it in the comment area, or filter out the popup information and display it on the video screen. However, because the bullet chat information has some special properties, it is impossible to directly use the existing comment interface directly. For example, the bullet chat may set the position displayed on the screen, the font color of the bullet chat, and so on. In this case, by constructing an Adapteradapter, on the basis of reusing the existing commenting capabilities, we can extend and realize the new features of the barrage that we need.

如上图所示,我们可以在Adapter中封装扩展弹幕需要的新特性,然后对于数据存储等逻辑则直接复用已有的评论功能处理逻辑,这样就可以大大减少我们的开发工作量、后续也只需要维护一套主体代码即可。

负重前行:兼容历史版本

和上面讨论的场景相反,实际开发中还有一种非常常见的情况,就是原先的时候实现了一套业务逻辑,然后因为业务变化或者系统重构,需要对底层具体实现逻辑进行大改。这种情况下,为了保证此前调用该API的业务可以正常使用,通常有两种思路:

  1. 保持原先的内容不动,完全另起炉灶全新实现一套,然后两套逻辑并存,同时维护;

  2. 按照新的逻辑去实现,并将原先的对外API适配转换对接使用新逻辑实现。

显然,从成本与可维护性层面考虑,思路2更为可行、更加经济

对比我们文首举的那个“插头转换器”的例子,我们可以把图中V1版本业务逻辑当做我们国内的手机充电插头,而图中绿色部分的V2新版本依赖逻辑,则是欧洲地区的圆孔墙面插座,那么如何让国标的扁口插头能用上欧标的圆孔插座呢?关键就是那个插头转换器(Adapter)。

另类心机:屏蔽开源协议传染

大家可以回想下,曾经是否也有过从github上“借鉴”一些代码放进了自己的项目中,然后简单修改为符合自己诉求的逻辑,便当做是自研代码去正常使用了?不知道你是否有关注过你所拷贝的代码所对应的开源协议呢?要小心啦、这个看似平常的操作,也许会给项目埋下致命隐患

为什么说的这么危言耸听呢?因为有一些不太友好的开源协议(比如GPL协议),会要求使用了其代码的项目如果商用就必须要开源其全部源码!而对于很多软件公司而言,源码便是公司的核心资产,是公司最为核心的竞争力,将源码开源无异于是要了老板和公司的命。也许有人会对此很不屑,大家都这么干,似乎并没有发现有人来追责呢?有个词叫做“树大招风”,只要你的产品做的够大,就一定会被盯上 —— 你品,你细品。在当前知识版权保护越来越强的情况下,我们还是应该关注并提前做好应对这种危机出现的可能,避免埋下隐患。

这种情况下,可以基于Adapter的机制,实现弃卒保车的效果。即构建一个适配层,然后仅将适配层进行开源,而核心的模块代码中,则通过接口调用的方式使用适配层即可,这样避免了核心模块代码被开源协议传染。由于核心模块中并没有集成被二次改动后的开源源码,所以也不具有开放源码的义务、而Adapter层没有任何核心业务逻辑,即使开源对公司、对项目也没有影响。

基于Adapter适配层的方式来切断开源协议传染的成功实践,最典型的莫过于Android项目(AOSP)了。因为AOSP是基于Linux kernel内核进行构建的,而Linux Kernel使用的是GPL协议,那么按照要求,AOSP也需要开源其源码。但是问题来了,如果AOSP开源源码了,势必导致所有基于Android定制的各个硬件厂商底层的设备驱动相关的代码也都要全部开源,显然不会有公司愿意这么干。

为了让各个公司可以放心的基于Android去开发自己的产品,AOSP将自己的协议搞成了Apache开源协议,这样对产商而言就非常友好了,无需将自己的核心源码开源。那么Google是如何做到将本来需要以GPL协议开源的AOSP给变为使用Apache协议开源的呢?其实就是做了一个Adapter —— 也即HALHardware Abstract Layer,硬件抽象层)。

Adapter是一种理念

关于编码中的Adapter,常规的文档或者资料中,往往都是指的狭义上的适配器,也就是代码class类维度的Adapter

我们跳出纯粹的编码层面,站到全局系统架构视角去审视的时候,其实Adapter在系统架构与编码设计中是一个比较宽泛的概念。我个人更愿意Adapter看做是一种问题解决的思想、一种方案设计的理念

根据要解决的问题level范围的不同,Adapter对应的粒度与呈现形态也会有差异。

服务型Adapter

如果是在一个分布式微服务系统中,消息推送能力可以预见的会提供给很多不同的服务节点去调用,则可以将消息推送能力也封装为一个对外微服务,业务通过RPC或者HTTP等方式进行远程调用。

这种是一种相对High Level的Adapter抽象使用(但抽象为服务独立部署后,其实也不仅仅是个Adapter了),广泛的应用于系统架构层面,是解决系统功能复用、业务解耦的一种有效手段。

在我此前的一篇文章中,介绍了一个构建通用在线文档预览服务的实际案例,里面对“预览编辑服务”的定位就是一个典型的服务型Adapter,如下图所示。通过预览编辑服务这个Adapter,将文档预览能力所涉及的后端对接OnlyOffice或者对接kkFileView等细节逻辑给屏蔽掉,业务服务通过Adapter进行调用,大大简化了业务的使用复杂度,也保持了业务模块与文档预览服务内部模块之间的耦合。

服务型Adapter着眼解决的是系统进程层面的适配与统一封装,自身既是一个Adapter,又是一个独立的服务,封装内部细节差异化的实现,保证其它进程服务相对简单的调用逻辑。

依赖库型Adapter

在一些中小型项目中,会有若干个业务模块中会用到消息发送的能力,但是整体体量与业务规划层面而言,却也无需单独部署一个专门的消息推送服务进程,这种情况下,可以将其封装为一个依赖库,比如JAVA中的一个jar包,或者C++中的一个so库文件,亦或是C#中的dll库文件。这样各个业务模块可以集成此库文件,直接进行API调用即可。

此种类型的Adapter实现,在很多的框架中非常常见。比如在JAVA中的SpringBoot中的日志框架,底层可以选择是使用logback,也可以选择切换到log4j

代码类Adapter

在单个项目模块中,我们为了保持业务逻辑的清晰与独立,也会通过Adapter类的方式,来解耦具体的业务逻辑。比如这里的消息推送服务,如果仅当前模块需要使用,则可以创建一个独立的Adapter类,提供接口供其他类调用,在Adapter类中完成具体逻辑的封装实现。

还是以前面举的告警通知消息发送的例子来说明,使用Adapter方式隔离消息通道与业务逻辑的实现UML图如下:

代码类的Adapter在实际项目中使用的场景非常的广泛,是用于屏蔽代码底层差异化逻辑的不二选择。在总结各种实际使用场景与优秀实践的基础上,演进为23种设计模式之一的适配器模式

下面我们一起聊一聊适配器模式。

Adapter是一种设计模式

所谓设计模式,便是将常规代码编码中常遇到的一些场景的处理方式进行了总结与抽象,固化成一个优秀实践范例模板,使其整体实现更符合设计原则的要求。也就是说:设计模式并非是凭空捏造的,其实就是来源于常规的编码实践总结

按照通俗意义上对代码设计模式的理解,适配器模式也可以分为2种形式,即类适配器模式对象适配器模式

下面分别阐述下。

类适配器模式

类适配器模式整体非常的简单,涉及的角色也很少。类适配器模式中,Adapter与被适配的Adaptee之间,通过继承的方式来实现,其UML图如下所示。

主要角色说明如下:

  • Adaptee:原始被适配的类,即不符合诉求需要由Adapter进行适配的原始接口

  • Adapter:适配器本身,也是类适配器模式的核心,用于将Adaptee适配为目标的Target。

  • Target:期待获取到的目标结果。也即Adaptee经由Adapter适配后得到的统一的目标接口

还是以前面的告警通知发送的场景为例,我们按照聚合的方式,演示下对应的Adapter实现逻辑。

@Service
public class MsgSendAdapter extends SmsSender implements IMsgSender {
    @Override
    public void send(AlarmDetail detail) {
        // detail转SMS请求体的逻辑
        SmsContent sms = convertToSmsContent(detail);
        super.sendSms(sms);
    }
}
复制代码

上述代码中,MsgSendAdapter继承了SmsSender类并且实现了IMsgSender接口,将父类SmsSender中的sendSms接口转换为了IMsgSender接口提供的目标接口send(),业务可以调用IMsgSender.send()接口,实现对SmsSender.sendSms()逻辑的调用。

对象适配器模式

对象适配器模式与类适配器模式类似,区别点在于Adapter与被适配的Adaptee之间非继承关系,而是对象组合关系。其UML图如下:

按照对象适配器的设计思路,其代码可以如下方式来实现:

@Service
public class MsgSendAdapter implements IMsgSender {
    @Autowired
    private SmsSender smsSender;

    @Override
    public void send(AlarmDetail detail) {
        // detail转SMS请求体的逻辑
        SmsContent sms = convertToSmsContent(detail);
        smsSender.sendSms(sms);
    }
}
复制代码

上述代码中,MsgSendAdapter类中以组合的方式持有SmsSender对象(Adaptee),相比较类适配器的继承逻辑,灵活性更高,所以对象适配器要更加的灵活与实用(其实在架构设计领域也一直有一种观点叫“组合优于继承”,因为继承打破了面向对象的封装)。

总结回顾

好啦,关于Adapter相关的讨论与个人的理解,这里就给大家分享到这里。Adapter不仅是一个简单的具体实现类,也不仅仅是23种设计模式之一,更是一种问题解决的思想、一种方案设计的理念。

关于本篇文档中的内容,不知道屏幕前的各位小伙伴是否在项目中有使用过Adapter或者Adapter模式来帮助自己实现某些功能呢?是否对Adapter还有一些别的独到见解呢?欢迎评论区留言一起交流下。

我是悟道,聊技术、又不仅仅聊技术~

If you find it useful, please like + follow to let me feel your support. You can also follow my official account [Architecture Enlightenment] for more timely updates.

Looking forward to discussing with you and growing into a better self together.

Guess you like

Origin juejin.im/post/7147846034931056677