編程范式巡禮第三季 談談依賴反轉

編程范式巡禮第三季 談談依賴反轉

今天會進入深一點的主題,談一個軟件開發的"道":依賴反轉。根據我的觀察,這也是架構師與程序員的分水嶺之一。

什么是依賴反轉

引出問題

讓我們從Uncle Bob和小明的一段對話開始。原文地址:如何成為一名優秀的架構師

小明:我要領導一個團隊,還要做所有關于數據庫、框架和Web服務器的重要決定。
Uncle Bob:好吧,如果是這樣,你就沒必要成為一名軟件架構師了。
小明:當然有必要了!我要成為一個能夠做所有重要決定的人。
Uncle Bob:這樣很好,只是你沒有列出哪些才是重要的決定。你剛才說的那些跟重要的決定沒有什么關系。

Bob大叔一上來就拋出了一個反常識的觀點,工具的選擇決策并不是重要的問題,為什么這么認為?

Uncle Bob:你認為業務邏輯依賴數據庫,但實際上不是這樣的。如果你的架構足夠好,最起碼業務邏輯不應該依賴數據庫。
小明:如果業務邏輯對數據庫一無所知,它怎么使用這些工具呢?
Uncle Bob:依賴反轉。你要讓數據庫依賴業務邏輯,而不是讓業務邏輯依賴數據庫。

Bob大叔認為重要的是業務邏輯的實現,工具只是服務于業務邏輯。但小明提出的是一個非常實際的問題,業務邏輯是基于工具來實現的,就好比畫家是依賴畫具創作的,東方和西方由于畫具不同,展現的藝術作品就完全不同。這個時候Bob大叔拋出了這次的重頭戲:依賴反轉。

小明:那就更加費解了!既然上層策略(假設你指的是業務邏輯)要調用下層策略(假設你指的是數據庫),那么就應該是上層策略依賴下層策略,就像調用者依賴被調用者一樣。這是眾所周知的!
Uncle Bob:在運行時確實是這樣的,但在編譯時我們要把依賴反轉過來。上層策略的代碼不要引用任何下層策略的代碼。

小明的說法有道理,適用于我們現實生活常識,但是計算機領域恰恰是有自己獨特規則的,Bob大叔指出了這一點。我們編寫的計算機代碼并不是直接運行的,當中會經過編譯器的處理,而這個中間處理,讓依賴反轉變成了可能。通俗點說,在計算機世界中,工具是可以晚于業務邏輯出現的。

代碼實例

下面來看代碼的例子:

小明:在Java里,發送者最起碼要知道接收者的基本類型。
Uncle Bob:是的。不過,不管是哪一種情況,發送者都不知道接收者具體的類型。

發送者(業務邏輯):BusinessRule
基本類型:BusinessRuleGateway
具體類型(工具):MySqlBusinessRuleGateway

package businessRules;
import entities.Something;
public class BusinessRule {  
    private BusinessRuleGateway gateway;  
    public BusinessRule(BusinessRuleGateway gateway) {    
        this.gateway = gateway;  }  
    public void execute(String id) {    
        gateway.startTransaction();    
        Something thing = gateway.getSomething(id);    
        thing.makeChanges();    
        gateway.saveSomething(thing);    
        gateway.endTransaction();  
    }
}
import entities.Something;
public interface BusinessRuleGateway {  
    Something getSomething(String id);  
    void startTransaction();  
    void saveSomething(Something thing);  
    void endTransaction();
}
package database;
import businessRules.BusinessRuleGateway;
import entities.Something;
public class MySqlBusinessRuleGateway implements BusinessRuleGateway {  
    public Something getSomething(String id) {    
        // 從MySQL里讀取一些數據  
    }  
    public void startTransaction() {    
        // 開始一個事務  
    }  
    public void saveSomething(Something thing) {    
        // 把數據保存到MySQL  
    }  
    public void endTransaction() {    
        // 結束事務  
    }
}

可以看到,業務邏輯BusinessRule是在運行時對工具MySqlBusinessRuleGateway進行調用的,但在編譯時,兩者并沒有依賴關系。

基本類型的問題

一切都是那么的完美,依賴確實反轉了,但是存在一個問題,就是多出了一個東西:基本類型BusinessRuleGateway。

小明:這樣的話,如果業務邏輯需要所有的工具,那么你必須把所有工具都放到Gateway接口里。
小明:這樣的話,你就會有很多接口,而且有很多實現類。
小明:這樣子很浪費時間!我為什么要這樣做呢?這樣只會增加更多的代碼。
Uncle Bob:這個叫作接口分離原則。每個業務邏輯只使用一部分數據庫工具,所以每個業務邏輯只定義能夠滿足需要的接口。

小明提出了一個開發中很實際的問題,基本類型是多余的代碼,會增加工作。Bob大叔則覺得,基本類型可以認為是對工具的需求,也是需要思考的部分。

小明:但首先要先決定使用什么數據庫、Web服務器或框架啊!
Uncle Bob:不,實際上應該在開發后期才開始做這些事情——在你掌握了更多信息之后。
哀哉,當架構師草率地決定要使用一個數據庫,后來卻發現使用文件系統效率更高。
哀哉,當架構師草率地決定使用一個Web服務器,后來卻發現團隊需要的不過是一個Socket接口。
哀哉,當架構師草率地決定使用一個框架,后來卻發現框架提供的功能是團隊不需要的,反而給團隊帶來了諸多約束。
幸哉,當架構師在掌握了足夠多的信息后才決定該用什么數據庫、Web服務器或框架。幸哉,當架構師為團隊鑒別出運行緩慢、耗費資源的IO設備和框架,這樣他們就可以構建飛速運行的輕量級測試環境。
幸哉,當架構師把注意力放在那些真正重要的事情上,并把那些不重要的事情放在一邊。

Bob大叔用一段詠嘆調結束了這一次對話,提出了他的核心看法:架構設計要能適應未來的變化。

  1. 在技術層面,最大的挑戰來自于無法預測的性能容量增長,需要不斷與更先進的工具進行接軌。
  2. 在業務層面,響應要求日益嚴峻,代碼的修改成本(主要由耦合帶來)會成為重要的生產力指標。
    解決思路是模塊與工具解耦、模塊與模塊解耦,依賴反轉無疑是實現解耦有力方法,是架構師的有力工具。

依賴反轉思想的擴展

依賴反轉不僅僅是一個模式或者方法,更重要的是其體現的解耦思想,下面再介紹兩個具有同樣思想的重要范式。

切面范式

切面Aspect是與程序的縱向主流執行方向橫向正交的關注焦點。此類代碼以片斷的形式散落在各處,雖具有相似的邏輯,卻無法用傳統的方法提煉成模塊。典型的例子如:日志輸出、代碼跟蹤、性能監控、異常處理、安全檢查、事務處理等。為解決此類問題,AOP應運而生。它將每類橫切關注點封裝到單獨的Aspect模塊中,通過定義執行點和代碼綁定起來。

AOP從描述來看是比較抽象的,簡單來說就是除了上面提到的模塊和工具、模塊和模塊以外,在模塊內代碼片斷之間也存在一定的依賴關系,而AOP是對代碼片斷解耦的方法。
下面是AOP代碼片斷(進行日志跟蹤)。

    @Around("execution(* spring.services.MyDemoService3.*(..))")
    public void traceBusiness(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("before enter method "+jp.getSignature().getName());
        jp.proceed(jp.getArgs());
        System.out.println("after enter method "+jp.getSignature().getName());
    }

有人會問了,為啥沒有業務邏輯。這是因為,AOP與業務邏輯是可以無關的,業務邏輯角度有可能無法感知。但是信息不透明容易形成惡性的發展,所以實際使用時要運用annotation進行限制,避免過度使用。

泛型范式

泛型編程是算法導向的,即以算法為起點和中心點,逐漸將其所涉及的概念內涵模糊化、外延擴大化,將其所涉及的運算抽象化、一般化,從而擴展算法的適用范圍。

泛型又是解耦思想在具體算法實現中的運用。算法在運行時是包含數據結構和算法邏輯兩個部分的,這個時候我們可以用基本類型來替代具體的數據結構,實現兩者的解耦。

下面是代碼例子(將一組Json字符轉換為對象),轉換的目標對象與算法邏輯是無關的,所以用基本類型T來進行了替代。

    static <T> List<T> convertJsonToPojo(List<String> jsonStrings, Class<T> c, boolean generateSeq) {
        List<T> result = new ArrayList<>();
        ObjectMapper objectMapper = ObjectMapperFactory.create();
        jsonStrings.forEach(map -> {
                result.add(convertJsonToPojo(map, c, generateSeq, objectMapper));
        });
        return result;
    }

泛型范式與日常工作非常接近,我們每時每刻都能接觸到,是除了基本范式之外最為古老的了。編寫泛型代碼一定會用到抽象思維,這不是一種常識思維,但可以作為一種練習,是由低到高的修煉捷徑。

小結

最后想說的是,最重要的不是依賴反轉這個方法,而是依賴反轉的思想。練成了這種思想,小處講,可以節省代碼、提高效率。大處講,可以適應變化、應對未來。這里只是拋個磚,請有志于架構設計的同學務必深入掌握。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容