当前位置: 首页 > 科技观察

Adapterincoding不仅仅是一种设计模式,更是一种架构理念和解决方案

时间:2023-03-12 20:34:29 科技观察

大家好,我们又见面了。不知道大家有没有看过或者用过下面的东西?这是一个插座转换器。我们都知道日常使用的是220v交流电,但是国外不同国家使用的电流电压不同(比如日本用的是110v),插座的接口样式也不同(比如欧洲国家用的是2小圆柱插头接口),如果我们去国外旅游,有了这个插座转换器,我们的手机充电器就可以在国外正常使用了。当然,除了使用插座适配器,还有一个方法可以让我们出国后也能正常使用各种电子产品,那就是在当地买一套新的!显然,这样的代价是巨大的,这显然不符合我们勤(nang)俭(zhong)养(xiu)家(se)的特点。看过我之前文章的朋友应该知道,在我的文章中,我反复阐述了自己的理念,那就是“编码源于生活”,这次也不例外。现实生活中朴素的哲学思考,其实无时无刻不在代码世界中得到体现。对于上面提到的例子,为什么这种情况在我们的项目中并不经常发生呢?我们先是按照原来的业务逻辑实现了一套代码,然后又来了一个新的需求。如果我们需要投入大量的人力物力去开发一套新的,第一选择就是思考如何复用已有的逻辑。以最低的成本与现有逻辑适配业务对接。在本文中,我们将从这个“套接字转换器”出发,谈谈软件系统中无处不在的“套接字转换器”——编码中的适配器(Adapter)。我选择以Adapter为主题来讲解,并不是因为Adapter的技术实现有多复杂。其实Adapter实现起来非常简单,很多人其实都有意无意地在使用它。更多的是想讨论一下这种利用Adapter来复用和兼容现有逻辑的思路,以及如何利用Adapter实践OCP(Open-ClosedPrinciple)的系统架构设计理念。Adapter包罗万象的新瓶装旧酒:复用现成的实现逻辑,将旧酒装新瓶,在我们的系统中是一个非常“省”的操作,可以让我们基于已有的能力快速打包提供产品。全新的商务功能。当然,有时候,系统现有的能力在某些方面可能并不能完全满足新业务的需求,需要进行一些转换和适配处理。例如:一个视频网站本来就有评论功能,用户可以在视频下方发表评论,然后评论的内容会以列表的形式显示在视频下方的页面上。现在需要开发一个新的功能,支持视频发送弹幕的功能,在视频播放画面上显示弹幕。在需求功能上,评论与弹幕有很多相似之处。对于后端,其处理逻辑和存储数据结构几乎相同,只是在实现数据列表API时,需要过滤掉评论信息显示在评论区,或者过滤掉弹幕信息并将其显示在视频屏幕上。但由于弹幕信息的特殊属性,无法直接使用现有的评论界面。比如弹幕可以设置在屏幕上显示的位置,弹幕的字体颜色等等。这种情况下,我们可以构建一个Adapter,在复用现有评论能力的基础上,扩展实现弹幕所需的新功能。如上图所示,我们可以将扩展弹幕需要的新特性封装在Adapter中,然后直接复用已有的评论功能处理逻辑进行数据存储等逻辑,可以大大减少我们的开发工作量和后续.只需要维护一套主体代码。重载前行:兼容历史版本与上面讨论的场景相反,在实际开发中还有一种很常见的情况,就是一开始实现了一套业务逻辑,后来由于业务变更或者系统重构,有必要对底层实现逻辑进行检修。在这种情况下,为了保证之前调用API的业务能够正常使用,通常有两种思路:保持原有内容不变,完全重新实现一套新的,然后将两套共存维护逻辑;按照新的逻辑来实现,并使用新的逻辑来实现原有的外部API适配转换对接。显然,从成本和可维护性的角度来看,思路2更为可行和经济。对比本文开头我们提到的“插头转换器”例子,我们可以把图中V1版本的业务逻辑看做是我们国产手机充电插头,而图中绿色部分的新版本V2数字取决于逻辑,这是欧洲地区。圆孔墙壁插座,那么如何让国标扁插头可以使用欧标圆孔插座呢?关键是插头转换器(Adapter)。另类心机:为了阻断开源协议的感染,你可以回想一下,你是否曾经从github上“借”过一些代码,放到自己的项目中,然后简单的修改成符合自己诉求的逻辑,这样,能不能算是自研代码才能正常使用?不知大家有没有关注过你复制的代码对应的开源协议?小心,这个看似普通的操作,可能为项目埋下了致命的隐患!你为什么这么危言耸听?因为有一些不友好的开源协议(比如GPL协议),会要求使用其代码的项目如果能商用,必须开源其所有源代码!对于很多软件公司来说,源代码是公司的核心资产,是公司的核心竞争力。开放源代码无异于杀死老板和公司。或许有些人对此会很不屑。每个人都在这样做,但似乎没有人来追究他们的责任?有句话叫“树大招风”,只要你的产品够大,就一定有针对性——你尝,你细细品味。在当前知识产权保护力度越来越大的情况下,我们还是应该关注并提前应对这种危机的可能性,以免埋下隐患。这样的话,基于Adapter的机制,就可以实现弃兵保车的效果了。即搭建一个适配层,然后只将适配层开源,在核心模块代码中,通过调用接口的方式使用适配层,避免核心模块代码被开源协议感染.由于核心模块没有集成二次修改的开源代码,因此没有开源代码的义务,Adapter层也没有任何核心业务逻辑,即使开源对公司也没有影响或项目。基于Adapter适配层切断开源协议感染最典型的成功实践是Android项目(AOSP)。由于AOSP是基于Linux内核构建的,而LinuxKernel使用的是GPL协议,根据需求,AOSP也需要开源其源代码。但问题来了。如果AOSP的源代码开源,势必导致各个硬件厂商的底层设备驱动相关代码全部基于Android定制。显然,没有公司愿意这样做。为了让企业有信心基于Android开发自己的产品,AOSP将自己的协议做成了Apache开源协议,对厂商非常友好,不需要开源自己的核心源代码。那么谷歌是如何把本应以GPL协议开源的AOSP,变成使用Apache协议开源的呢?其实它就是一个Adapter——也就是HAL(HardwareAbstractLayer,硬件抽象层)。Adapter是编码中关于Adapter的概念。在常规的文档或资料中,往往指的是狭义的适配器,即代码类维度中的适配器。当我们跳出纯编码层面,从整体系统架构的角度来看,Adapter其实是系统架构和编码设计中一个比较宽泛的概念。个人更倾向于把Adapter看作是一种解决问题的思路,一种程序设计的概念。根据所要解决问题的层次和范围,Adapter的粒度和呈现形式也会有所不同。如果服务型适配器在分布式微服务系统中,可以预见消息推送能力提供给很多不同的服务节点调用,那么消息推送能力也可以封装为一个外部微服务,业务可以通过RPC或HTTP等方式进行远程调用。这是一个比较高层次的Adapter抽象(但是抽象作为服务独立部署之后,就不仅仅是一个Adapter了),在系统架构层面被广泛使用,是系统功能复用和业务解耦的一种解决方案。一种有效的手段。在我之前的一篇文章中,介绍了一个搭建通用在线文档预览服务的实际案例,其中“预览编辑服务”的定位是一个典型的服务Adapter,如下图所示。通过预览编辑服务的Adapter,屏蔽了文档预览能力中涉及到的后端对接OnlyOffice或者对接kkFileView的详细逻辑,通过Adapter调用业务服务,大大简化了业务使用的复杂度和维护业务模块。与文档预览服务的内部模块耦合。服务适配器侧重于系统进程层面的适配和统一封装。它既是一个适配器又是一个独立的服务,封装了内部差异化细节的实现,保证了其他流程服务的调用逻辑相对简单。根据库适配器的不同,在一些中小型项目中,会有几个业务模块会使用发送消息的能力,但从整体体量和业务规划上来说,没有必要部署专门的消息推送服务流程分开。这种情况下可以打包成依赖库,比如JAVA中的jar包,或者C++中的so库文件,C#中的dll库文件。这样各个业务模块就可以集成这个库文件,直接调用API。这种类型的Adapter实现在很多框架中都很常见。比如在JAVA中SpringBoot的日志框架中,底层可以选择使用logback或者切换到log4j。代码类Adapter在单个项目模块中。为了保持业务逻辑的清晰和独立,我们还会使用Adapter类来解耦具体的业务逻辑。比如这里的消息推送服务,如果只需要使用当前模块,可以创建一个独立的Adapter类,提供接口供其他类调用,在Adapter类中完成具体逻辑的封装和实现。还是用上面提到的发送告警通知消息的例子来说明,使用Adapter方法隔离消息通道和业务逻辑的实现UML图如下:代码型Adapter应用场景广泛在实际项目中,用于屏蔽底层代码。微分逻辑的明显选择。在总结各种实际使用场景和良好实践的基础上,演变为适配器模式,23种设计模式之一。下面一起来聊聊适配器模式。适配器是一种设计模式。所谓设计模式,就是将常规代码编码中经常遇到的一些场景的处理方法进行归纳和抽象,固化成一个优秀的实践范例模板,使整体实现更符合设计原则的要求。.也就是说:设计模式不是凭空捏造出来的,而是实实在在地从常规的编码实践中总结出来的。按照通俗意义上的代码设计模式的理解,适配器模式也可以分为两种形式,即类适配器模式和对象适配器模式。下面分别说明。类适配器模式类适配器模式整体上非常简单,涉及的角色也很少。在类适配器模式下,Adapter与被适配的Adaptee之间的关系是通过继承来实现的,其UML图如下所示。主要作用解释如下:Adaptee:原适配类,即原接口不符合要求,需要Adapter适配。Adapter:适配器本身也是类适配器模式的核心,用于将Adaptee适配为目标的Target。目标:期望获得的目标结果。即Adaptee经过Adapter适配后得到的统一目标接口。还是以之前发送告警通知的场景为例,我们以聚合的方式演示相应的Adapter实现逻辑。@ServicepublicclassMsgSendAdapterextendsSmsSenderimplementsIMsgSender{@Overridepublicvoidsend(AlarmDetaildetail){//detail到短信请求体的逻辑SmsContentsms=convertToSmsContent(detail);super.sendSms(短信);}}上述代码中,MsgSendAdapter继承了SmsSender类并实现了IMsgSender接口,将父类SmsSender中的sendSms接口转换为IMsgSender接口提供的目标接口send(),以及业务可以调用IMsgSender.send()接口实现SmsSender.sendSms()逻辑调用。对象适配器模式对象适配器模式类似于类适配器模式,不同的是Adapter和被适配的Adaptee之间没有继承关系,而是对象组合关系。其UML图如下:根据对象适配器的设计思路,其代码可以实现如下:@ServicepublicclassMsgSendAdapterimplementsIMsgSender{@AutowiredprivateSmsSendersmsSender;@Overridepublicvoidsend(AlarmDetaildetail){//短信请求体的详细信息SmsContent的逻辑sms=convertToSmsContent(detail);smsSender.sendSms(sms);}}在上面的代码中,MsgSendAdapter类以组合的方式持有SmsSender对象(Adaptee)。与类适配器的继承逻辑相比,它的灵活性更高,所以对象适配器应该更灵活实用(其实在架构设计领域一直有“组合胜于继承”的观点,因为继承打破了面向对象的封装)。总结和回顾,我将把Adapter相关的讨论和个人理解分享给大家。Adapter不仅仅是一个简单的具体实现类,它不仅是23种设计模式之一,更是一种解决问题的思想,一种程序设计的理念。关于这个文档的内容,不知道屏幕前的小伙伴们有没有在项目中使用过Adapter或者Adapter模式来帮助自己实现某些功能呢?您对Adapter还有其他一些独到的见解吗?

猜你喜欢