天天看點

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

作者:架構悟道

大家好,又見面了。

不知道下面這玩意大家有沒有見過或者使用過?這是一個插座轉換器。我們都知道日常使用的是220v的交流電,而國外不同國家使用的電流電壓是不一樣的(比如日本使用的是110v)、且插座的接口樣式也是各不相同的(比如歐洲國家使用的是兩個小圓柱狀的插頭接口),如果我們到别的國家去旅行的時候,借助這個插座轉換器,就可以讓我們的手機充電器在國外也能正常使用了。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

當然,除了使用插座轉換器,還有個方法也可以讓我們出國之後正常地使用各種電子産品,那就是在當地重新買一套!顯然,這樣的成本就會非常巨大,明顯不符合我們 勤(nang)儉(zhong)持(xiu)家(se) 的特征。

看過我前面的文章的小夥伴應該知道,我的文章中一直反複地在闡述自己的一個觀念,即“編碼源于生活” ,這裡依舊不例外。現實生活中的樸素哲學思維,在代碼世界中其實也無時無刻不在展現着。上面舉的例子,在我們的項目中又何嘗不是在頻繁上演此類情況呢?

我們先按照原有的業務邏輯實作了一套代碼,後來又來了個新的需求,如果重新開發一套需要投入大量的人力物力,是以首選方案就是去思考如何去複用已有的邏輯,以最小的代價将業務對接适配使用現有的邏輯去實作。

本篇文章中,我們就從這個“插座轉換器”來作為切入點,聊一聊在軟體系統中無處不在的“插座轉換器” —— 編碼中的擴充卡(Adapter)。標明以Adapter為題材進行闡述,并非是因為Adapter在技術實作上有多複雜,其實Adapter真正實作起來是非常簡單的,而且很多人有意或無意中其實也都在使用。更多的是想一起探讨這種借助Adapter來複用與相容已有邏輯的思路,以及如何利用Adapter來踐行OCP(開閉原則)的系統架構設計理念。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

Adapter的百媚千姿

新瓶舊酒:複用現成的實作邏輯

新瓶裝舊酒,在我們的系統裡面是一個很“節省”的操作,可以讓我們基于一個現有的能力快速的封裝提供出一個全新的業務功能,當然有的時候,系統現有的能力可能會某些方面無法完全滿足新業務的需求,需要做一些轉換适配處理。

舉個例子:

一個視訊網站,原先已有一個評論能力,使用者可以在視訊下方發表評論,然後評論内容以清單的形式展示在視訊下方頁面上。現在需要開發一個新功能,支援視訊發送彈幕能力,并将彈幕顯示在視訊播放畫面上。

從需求功能上來說,評論與彈幕有很多相似之處。對後端而言,其處理邏輯與存儲資料結構幾乎都是相同的,隻是在資料清單API實作的時候,需要過濾出評論資訊展示到評論區、或者過濾出彈幕資訊顯示到視訊畫面上。但是由于彈幕資訊有一些特殊的屬性,又沒法直接完全使用現有的評論接口,比如彈幕可能會設定顯示在螢幕的位置、彈幕的字型顔色等等。這種情況下,我們可以通過構造個Adapter擴充卡的方式,在複用已有評論能力的基礎上,順便擴充實作需要的彈幕新特性。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

如上圖所示,我們可以在Adapter中封裝擴充彈幕需要的新特性,然後對于資料存儲等邏輯則直接複用已有的評論功能處理邏輯,這樣就可以大大減少我們的開發工作量、後續也隻需要維護一套主體代碼即可。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

負重前行:相容曆史版本

和上面讨論的場景相反,實際開發中還有一種非常常見的情況,就是原先的時候實作了一套業務邏輯,然後因為業務變化或者系統重構,需要對底層具體實作邏輯進行大改。這種情況下,為了保證此前調用該API的業務可以正常使用,通常有兩種思路:

  1. 保持原先的内容不動,完全另起爐竈全新實作一套,然後兩套邏輯并存,同時維護;
  2. 按照新的邏輯去實作,并将原先的對外API适配轉換對接使用新邏輯實作。

顯然,從成本與可維護性層面考慮,思路2更為可行、更加經濟。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

對比我們文首舉的那個“插頭轉換器”的例子,我們可以把圖中V1版本業務邏輯當做我們國内的手機充電插頭,而圖中綠色部分的V2新版本依賴邏輯,則是歐洲地區的圓孔牆面插座,那麼如何讓國标的扁口插頭能用上歐标的圓孔插座呢?關鍵就是那個插頭轉換器(Adapter)。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

另類心機:屏蔽開源協定傳染

大家可以回想下,曾經是否也有過從github上“借鑒”一些代碼放進了自己的項目中,然後簡單修改為符合自己訴求的邏輯,便當做是自研代碼去正常使用了?不知道你是否有關注過你所拷貝的代碼所對應的開源協定呢?要小心啦、這個看似平常的操作,也許會給項目埋下緻命隐患!

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

為什麼說的這麼危言聳聽呢?因為有一些不太友好的開源協定(比如GPL協定),會要求使用了其代碼的項目如果商用就必須要開源其全部源碼!而對于很多軟體公司而言,源碼便是公司的核心資産,是公司最為核心的競争力,将源碼開源無異于是要了老闆和公司的命。也許有人會對此很不屑,大家都這麼幹,似乎并沒有發現有人來追責呢?有個詞叫做“樹大招風”,隻要你的産品做得夠大,就一定會被盯上 —— 你品,你細品。在目前知識版權保護越來越強的情況下,我們還是應該關注并提前做好應對這種危機出現的可能,避免埋下隐患。

這種情況下,可以基于Adapter的機制,實作棄卒保車的效果。即建構一個适配層,然後僅将适配層進行開源,而核心的子產品代碼中,則通過接口調用的方式使用适配層即可,這樣避免了核心子產品代碼被開源協定傳染。由于核心子產品中并沒有內建被二次改動後的開源源碼,是以也不具有開放源碼的義務、而Adapter層沒有任何核心業務邏輯,即使開源對公司、對項目也沒有影響。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

基于Adapter适配層的方式來切斷開源協定傳染的成功實踐,最典型的莫過于Android項目(AOSP)了。因為AOSP是基于Linux kernel核心進行建構的,而Linux Kernel使用的是GPL協定,那麼按照要求,AOSP也需要開源其源碼。但是問題來了,如果AOSP開源源碼了,勢必導緻所有基于Android定制的各個硬體廠商底層的裝置驅動相關的代碼也都要全部開源,顯然不會有公司願意這麼幹。

為了讓各個公司可以放心的基于Android去開發自己的産品,AOSP将自己的協定搞成了Apache開源協定,這樣對産商而言就非常友好了,無需将自己的核心源碼開源。那麼Google是如何做到将本來需要以GPL協定開源的AOSP給變為使用Apache協定開源的呢?其實就是做了一個Adapter —— 也即HAL(Hardware Abstract Layer,硬體抽象層)。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

Adapter是一種理念

關于編碼中的Adapter,正常的文檔或者資料中,往往都是指的狹義上的擴充卡,也就是代碼class類次元的Adapter。

我們跳出純粹的編碼層面,站到全局系統架構視角去審視的時候,其實Adapter在系統架構與編碼設計中是一個比較寬泛的概念。我個人更願意Adapter看做是一種問題解決的思想、一種方案設計的理念。

根據要解決的問題level與範圍的不同,Adapter對應的粒度與呈現形态也會有差異。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

服務型Adapter

如果是在一個分布式微服務系統中,消息推送能力可以預見的會提供給很多不同的服務節點去調用,則可以将消息推送能力也封裝為一個對外微服務,業務通過RPC或者HTTP等方式進行遠端調用。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

這種是一種相對High Level的Adapter抽象使用(但抽象為服務獨立部署後,其實也不僅僅是個Adapter了),廣泛的應用于系統架構層面,是解決系統功能複用、業務解耦的一種有效手段。

在我此前的一篇文章中,介紹了一個建構通用線上文檔預覽服務的實際案例,裡面對“預覽編輯服務”的定位就是一個典型的服務型Adapter,如下圖所示。通過預覽編輯服務這個Adapter,将文檔預覽能力所涉及的後端對接OnlyOffice或者對接kkFileView等細節邏輯給屏蔽掉,業務服務通過Adapter進行調用,大大簡化了業務的使用複雜度,也保持了業務子產品與文檔預覽服務内部子產品之間的耦合。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

服務型Adapter着眼解決的是系統程序層面的适配與統一封裝,自身既是一個Adapter,又是一個獨立的服務,封裝内部細節差異化的實作,保證其它程序服務相對簡單的調用邏輯。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

依賴庫型Adapter

在一些中小型項目中,會有若幹個業務子產品中會用到消息發送的能力,但是整體體量與業務規劃層面而言,卻也無需單獨部署一個專門的消息推送服務程序,這種情況下,可以将其封裝為一個依賴庫,比如JAVA中的一個jar包,或者C++中的一個so庫檔案,亦或是C#中的dll庫檔案。這樣各個業務子產品可以內建此庫檔案,直接進行API調用即可。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

此種類型的Adapter實作,在很多的架構中非常常見。比如在JAVA中的SpringBoot中的日志架構,底層可以選擇是使用logback,也可以選擇切換到log4j。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

代碼類Adapter

在單個項目子產品中,我們為了保持業務邏輯的清晰與獨立,也會通過Adapter類的方式,來解耦具體的業務邏輯。比如這裡的消息推送服務,如果僅目前子產品需要使用,則可以建立一個獨立的Adapter類,提供接口供其他類調用,在Adapter類中完成具體邏輯的封裝實作。

還是以前面舉的告警通知消息發送的例子來說明,使用Adapter方式隔離消息通道與業務邏輯的實作UML圖如下:

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

代碼類的Adapter在實際項目中使用的場景非常的廣泛,是用于屏蔽代碼底層差異化邏輯的不二選擇。在總結各種實際使用場景與優秀實踐的基礎上,演進為23種設計模式之一的擴充卡模式。

下面我們一起聊一聊擴充卡模式。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

Adapter是一種設計模式

所謂設計模式,便是将正常代碼編碼中常遇到的一些場景的處理方式進行了總結與抽象,固化成一個優秀實踐範例模闆,使其整體實作更符合設計原則的要求。也就是說:設計模式并非是憑空捏造的,其實就是來源于正常的編碼實踐總結。

按照通俗意義上對代碼設計模式的了解,擴充卡模式也可以分為2種形式,即類擴充卡模式與對象擴充卡模式。

下面分别闡述下。

類擴充卡模式

類擴充卡模式整體非常的簡單,涉及的角色也很少。類擴充卡模式中,Adapter與被适配的Adaptee之間,通過繼承的方式來實作,其UML圖如下所示。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

主要角色說明如下:

  • 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,是一種設計模式,更是一種架構理念與解決方案

對象擴充卡模式

對象擴充卡模式與類擴充卡模式類似,差別點在于Adapter與被适配的Adaptee之間非繼承關系,而是對象組合關系。其UML圖如下:

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

按照對象擴充卡的設計思路,其代碼可以如下方式來實作:

@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相關的讨論與個人的了解,這裡就給大家分享到這裡。Adapter不僅是一個簡單的具體實作類,也不僅僅是23種設計模式之一,更是一種問題解決的思想、一種方案設計的理念。

關于本篇文檔中的内容,不知道螢幕前的各位小夥伴是否在項目中有使用過Adapter或者Adapter模式來幫助自己實作某些功能呢?是否對Adapter還有一些别的獨到見解呢?歡迎評論區留言一起交流下。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

我是悟道,聊技術、又不僅僅聊技術~

如果覺得有用,請點贊 + 關注讓我感受到您的支援。也可以關注下我的公衆号【架構悟道】,擷取更及時的更新。

期待與你一起探讨,一起成長為更好的自己。

編碼中的Adapter,是一種設計模式,更是一種架構理念與解決方案

繼續閱讀