今天會進入深一點的主題,談一個軟件開發的"道":依賴反轉。根據個人觀察,這也是架構師與程序員的分水嶺之一。java
讓咱們從Uncle Bob和小明的一段對話開始。原文地址:如何成爲一名優秀的架構師程序員
小明:我要領導一個團隊,還要作全部關於數據庫、框架和Web服務器的重要決定。
Uncle Bob:好吧,若是是這樣,你就不必成爲一名軟件架構師了。
小明:固然有必要了!我要成爲一個可以作全部重要決定的人。
Uncle Bob:這樣很好,只是你沒有列出哪些纔是重要的決定。你剛纔說的那些跟重要的決定沒有什麼關係。算法
Bob大叔一上來就拋出了一個反常識的觀點,工具的選擇決策並非重要的問題,爲何這麼認爲?spring
Uncle Bob:你認爲業務邏輯依賴數據庫,但實際上不是這樣的。若是你的架構足夠好,最起碼業務邏輯不該該依賴數據庫。
小明:若是業務邏輯對數據庫一無所知,它怎麼使用這些工具呢?
Uncle Bob:依賴反轉。你要讓數據庫依賴業務邏輯,而不是讓業務邏輯依賴數據庫。數據庫
Bob大叔認爲重要的是業務邏輯的實現,工具只是服務於業務邏輯。但小明提出的是一個很是實際的問題,業務邏輯是基於工具來實現的,就比如畫家是依賴畫具創做的,東方和西方因爲畫具不一樣,展示的藝術做品就徹底不一樣。這個時候Bob大叔拋出了此次的重頭戲:依賴反轉。編程
小明:那就更加費解了!既然上層策略(假設你指的是業務邏輯)要調用下層策略(假設你指的是數據庫),那麼就應該是上層策略依賴下層策略,就像調用者依賴被調用者同樣。這是衆所周知的!
Uncle Bob:在運行時確實是這樣的,但在編譯時咱們要把依賴反轉過來。上層策略的代碼不要引用任何下層策略的代碼。json
小明的說法有道理,適用於咱們現實生活常識,可是計算機領域偏偏是有本身獨特規則的,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大叔用一段詠歎調結束了這一次對話,提出了他的核心見解:架構設計要能適應將來的變化。
依賴反轉不只僅是一個模式或者方法,更重要的是其體現的解耦思想,下面再介紹兩個具備一樣思想的重要範式。
切面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; }
泛型範式與平常工做很是接近,咱們每時每刻都能接觸到,是除了基本範式以外最爲古老的了。編寫泛型代碼必定會用到抽象思惟,這不是一種常識思惟,但能夠做爲一種練習,是由低到高的修煉捷徑。
最後想說的是,最重要的不是依賴反轉這個方法,而是依賴反轉的思想。練成了這種思想,小處講,能夠節省代碼、提升效率。大處講,能夠適應變化、應對將來。這裏只是拋個磚,請有志於架構設計的同窗務必深刻掌握。