コーディングにおけるアダプタは設計パターンですが、アーキテクチャの概念とソリューションでもあります

Golden Stone Project の最初のチャレンジに参加するためにサインアップしました - 賞金プール 100,000 を共有します。これは私の 4 回目の記事です。クリックしてイベントの詳細を表示します


みなさんこんにちは、また会いましょう。

次のものを見たり使用したことがあるかどうかわかりませんか? ソケットコンバーターです220v交流が日常的に使用されていることは誰もが知っていますが、海外のさまざまな国で使用される電流と電圧は異なり (日本で使用されているものなど110v)、ソケットのインターフェイス スタイルも異なります (ヨーロッパ諸国で使用されているものは 2 つです)。小さな円筒形プラグ インターフェイス)、このソケット アダプターの助けを借りて、他の国に旅行する場合、携帯電話の充電器を海外で正常に動作させることができます。

もちろん、ソケットコンバータを使用する以外にも、海外に行った後もさまざまな電化製品を正常に使用できるようにする別の方法があります。それは、新しいセットを現地で購入することです! 明らかに、そのようなコストは非常に大きく、明らかに勤勉 ( nang ) 倹約 ( zhong ) サポート ( xiu ) 家族 ( se ) の特性と一致しません。

私の以前の記事を読んだ友人は、私の記事が私自身の概念の 1 つ、つまり「コーディングは人生から生まれる」を繰り返し説明してきたことを知っているはずであり、これも例外ではありません。実生活における単純な哲学的思考は、コードの世界に常に反映されています。上記の例で、私たちのプロジェクトでは、このような状況が頻繁に演出されていませんか?

最初に元のビジネス ロジックに従って一連のコードを実装した後、新しい要件が発生しました。新しいコード セットを開発する場合、多くの人的資源と物的リソースを投資する必要があるため、最初の選択は考えることです。既存ロジックの再利用方法について 既存ロジックを活用し、最小コストで業務ドッキング適応を実現します。

この記事では、この「ソケット コンバーター」をエントリ ポイントとして使用して、ソフトウェア システムに遍在する「ソケット コンバーター」、つまりコーディングのアダプターについて説明します ( Adapter)。技術的な実装が複雑だからというわけAdapterはなく、実際に実現するのは非常に簡単であり、実際に多くの人が意図的または非意図的に使用しています。さらに、 Adapter を使用して既存のロジックを再利用して互換性を持たせるというこのアイデアと、OCP (Open-Closed Principle) のシステム アーキテクチャ設計概念を実践するために Adapter を使用する方法について説明したいと思います。AdapterAdapter

アダプターの美しさ

新しいボトルに入った古いワイン: 既製の実装ロジックの再利用

古いワインを新しいボトルに詰めることは、私たちのシステムでは非常に「節約」作業であり、既存の機能に基づいて新しいビジネス機能を迅速にカプセル化することができます.新しいビジネスのニーズを満たすため、一部の変換および適応処理が必要です。

例えば:

動画サイトにはもともとコメント機能があり、ユーザーは動画の下にコメントを投稿でき、コメントの内容は動画の下のページに一覧形式で表示されます。これからは、ビデオが弾幕を送信し、ビデオ再生画面に弾幕を表示する機能をサポートする新しい機能を開発する必要があります。

必要な機能に関しては、コメントはバラージ多くの類似点があります。バックエンドについては、処理ロジックと格納データ構造はほぼ同じですが、データを列表API実現する際に、コメント情報をフィルタリングしてコメント領域に表示するか、ポップアップ情報をフィルタリングして表示する必要があります。ビデオ画面で。ただし、バレットチャット情報は特殊な性質を持っているため、既存のコメントインターフェースを直接利用することはできません.例えば、バレットチャットは画面に表示する位置やバレットチャットの文字色などを設定する場合があります. . この場合、Adapterアダプターを構築することで、既存のコメント機能の再利用に基づいて、必要な弾幕の新しい機能を拡張および実現できます。

如上图所示,我们可以在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还有一些别的独到见解呢?欢迎评论区留言一起交流下。

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

役に立つと思われる場合は、いいね+ フォローして、あなたのサポートを実感してください。私の公式アカウント [Architecture Enlightenment] をフォローして、タイムリーな更新を確認することもできます。

あなたと話し合って、より良い自分に成長できることを楽しみにしています。

おすすめ

転載: juejin.im/post/7147846034931056677