大話設計模式之依賴倒轉模式

一、定義編程

  • 高層模塊不該該依賴底層模塊,兩個都應該依賴於抽象
  • 抽象不該該依賴細節,細節應該依賴抽象

高層模塊和低層模塊容易理解,每個邏輯的實現都是由原子邏輯組成的,不可分割的原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊。那什麼是抽象,什麼又是細節呢?在Java語言中,抽象就是指接口或抽象類,二者都是不能直接被實例化的;細節就是實現類,實現接口或繼承抽象類而產生的類就是細節,其特色就是能夠直接被實例化,也就是能夠加上一個關鍵字new產生一個對象。依賴倒置原則在Java語言中的表現就是:架構

  • 模塊間的依賴是經過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的;
  • 接口或抽象類不依賴於實現類;
  • 實現類依賴接口或抽象類。

     更加精簡的定義就是「面向接口編程」——OOD(Object-Oriented Design,面向對象設計)的精髓之一。ide

二、里氏替換原則模塊化

說明:一個軟件的實體,若是使用的一個類是父類的話,那麼必定適用於其子類,並且它察覺不出父類對象和子類對象的區別。也就是說,在軟件裏面,把父類都替換成它的子類,程序的行爲沒有變化工具

里氏代換原則:(LSP):子類必須可以替換掉它們的父類型【ASD】單元測試

意思是:一件事情,父類能幹,子類也必定要能幹嗎,若是不是那麼子類就不該該繼承父類。 測試

正有了這個原則使得繼承複用成爲了可能。只有當子類能夠替換掉父類,軟件單位的功能不受到影響時,父類才能真正被複用,而子類也可以在父類的基礎上增長新的行爲。spa

需求變化時,只須繼承,而別的東西不會改變。因爲里氏代換原則才使得開放封閉成爲可能。這樣使得子類在父類無需修改的話就能夠擴展。這就是電腦壞了菜鳥也能夠修,收音機壞了只有專家能修改的道理。電腦是模塊化的,模塊之間依賴性不強。而收音機依賴性很強,對以維護。線程

採用依賴倒置原則能夠減小類間的耦合性,提升系統的穩定性,減小並行開發引發的風險,提升代碼的可讀性和可維護性。設計

三、言而無信,你太須要契約

     證實一個定理是否正確,有兩種經常使用的方法:一種是根據提出的論題,通過一番論證,推出和定理相同的結論,這是順推證法;還有一種是首先假設提出的命題是僞命題,而後推導出一個荒謬、與已知條件互斥的結論,這是反證法。咱們今天就用反證法來證實依賴倒置原則是多麼的優秀和偉大!

     論題:依賴倒置原則能夠減小類間的耦合性,提升系統的穩定性,減小並行開發引發的風險,提升代碼的可讀性和維護性。

     反論題:不使用依賴倒置原則也能夠減小類間的耦合性,提升系統的穩定性,減小並行開發引發的風險,提升代碼的可讀性和維護性。

     咱們經過一個例子來講明反論題是不成立的。如今的汽車愈來愈便宜了,也就頂多一個衛生間的價格就能夠買到一輛不錯的汽車,有汽車就必然有人來駕駛了,司機駕駛奔馳車的類圖如圖3-1所示。

                                         

                       

圖3-1 司機駕駛奔馳車類圖

     奔馳車能夠提供一個方法run,表明車輛運行,實現過程如代碼清代3-1所示。

1)司機代碼:

public class Driver
    {
        public void drive(Benz benz)
        {
            benz.run();
        }
    }
View Code

2)奔馳汽車代碼:

public class Benz
    {
        public void run()
        {
            Console.WriteLine("奔馳汽車運行");
        }
    }
View Code

   有車,有司機,在Client場景類產生相應的對象,其源代碼如代碼清代3-3所示。

public class Client
    {
        static void Main(string[] args)
        {
            Driver driver = new Driver();

            Benz benz = new Benz();
            driver.drive(benz);
        }
    }
View Code

     經過以上的代碼,完成了司機開動奔馳車的場景,到目前爲止,這個司機開奔馳車的項目沒有任何問題。咱們常說「危難時刻見真情」,咱們把這句話移植到技術上就成了「變動才顯真功夫」,業務需求變動永無休止,技術前進就永無止境,在發生變動時才能發覺咱們的設計或程序是不是鬆耦合。咱們在一段貌似磐石的程序上加上一塊小石頭:張三司機不只要開奔馳車,還要開寶馬車,又該怎麼實現呢?麻煩出來了,那好,咱們走一步是一步,咱們先把寶馬車產生出來,實現過程如代碼清單3-4所示。

public class BMW
    {
        public void run()
        {
            Console.WriteLine("寶馬車開始運行");
        }
    }
View Code

寶馬車也產生了,可是咱們卻沒有辦法讓張三開動起來,爲何?張三沒有開動寶馬車的方法呀!一個拿有C照的司機居然只能開奔馳車而不能開寶馬車,這也太不合理了!在現實世界都不容許存在這種狀況,況且程序仍是對現實世界的抽象,咱們的設計出現了問題:司機類和奔馳車類之間是一個緊耦合的關係,其致使的結果就是系統的可維護性大大下降,可讀性下降,兩個類似的類須要閱讀兩個文件,你樂意嗎?還有穩定性,什麼是穩定性?固化的、健壯的纔是穩定的,這裏只是增長了一個車類就須要修改司機類,這不是穩定性,這是易變性。被依賴者的變動居然讓依賴者來承擔修改的成本,這樣的依賴關係誰肯承擔!證實到這裏,咱們已經知道僞命題已經部分不成立了。

     注意 設計是否具有穩定性,只要適當的「鬆鬆土」,觀察「設計的藍圖」是否還能夠茁壯的成長就能夠得出結論,穩定性較高的設計,在周圍環境頻繁變化的時候,依然能夠作到「我自巋然不動」。

     咱們繼續證實,「減小並行開發引發的風險」,什麼是並行開發的風險?並行開發最大的風險就是風險擴散,原本只是一段程序的錯誤或異常,逐步波及一個功能,一個模塊,甚至到最後毀壞了整個項目,爲何並行開發就有這個風險呢?一個團隊,20人開發,各人負責不一樣的功能模塊,甲負責汽車類的建造,乙負責司機類的建造,在甲沒有完成的狀況下,乙是不能徹底地編寫代碼的,缺乏汽車類,編譯器根本就不會讓你經過!在缺乏Benz類的狀況下,Driver類能編譯嗎?更不要說是單元測試了!在這種不使用依賴倒置原則的環境中,全部的開發工做都是「單線程」的,甲作完,乙再作,而後是丙繼續…,這在90年代「我的英雄主義」編程模式中仍是比較適用的,一我的完成全部的代碼工做,但在如今的大中型項目中已是徹底不能勝任了,一個項目是一個團隊的協做結果,一個「英雄」再牛X也不可能瞭解全部的業務和全部的技術,要協做就要並行開發,要並行開發就要解決模塊之間的項目依賴關係,那而後呢?依賴倒置原則就隆重出場了!

根據以上證實,若是不使用依賴倒置原則就會加劇類間的耦合性,下降系統的穩定性,增長並行開發引發的風險,下降代碼的可讀性和維護性。承接上面的例子,引入依賴倒置原則後的類圖如圖3-2所示。

                                                      

                                                                        圖3-2 引入依賴倒置原則後的類圖

創建兩個接口:IDriver和ICar,分別定義了司機和汽車的各個職能,司機就是駕駛汽車,必須實現drive()方法,其實現過程如代碼清單3-5所示。

代碼清單3-5 司機接口

public interface IDriver
    {
        void Drive(ICar car);
    }
View Code

接口只是一個抽象化的概念,是對一類事物的最抽象描述,具體的實現代碼由相應的實現類來完成,Driver實現類如代碼清單3-6所示。

代碼清單3-6 司機類的實現

public class Driver : IDriver
    {
        public void Drive(ICar car)
        {
            car.run();
        }
    }
View Code

在IDriver中,經過傳入ICar接口實現了抽象之間的依賴關係,Driver實現類也傳入了ICar接口,至於究竟是哪一個型號的Car須要在高層模塊中聲明。

     ICar及其兩個實現類的實現過程如代碼清單3-7所示。

代碼清單3-7 汽車接口及兩個實現類

public interface ICar
    {
        void run();
    }
View Code
public class Benz:ICar
    {
        public void run()
        {
            Console.WriteLine("奔馳車開始運行");
        }
    }

public class BMW :ICar
    {
        public void run()
        {
            Console.WriteLine("寶馬車開始運行");
        }
    }
View Code

在業務場景中,咱們貫徹「抽象不該該依賴細節」,也就是咱們認爲抽象(ICar接口)不依賴BMW和Benz兩個實現類(細節),所以咱們在高層次的模塊中應用都是抽象,Client的實現過程如代碼清單3-8所示

class Client
    {
        static void Main(string[] args)
        {
            IDriver driver = new Driver();

            ICar benz = new Benz();
            ICar bmw = new BMW();

            driver.Drive(benz);
            driver.Drive(bmw);
        }
    }
View Code

四、最佳實踐

     依賴倒轉原則的本質就是經過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的鬆耦合,咱們怎麼在項目中使用這個規則呢?只要遵循如下的幾個規則就能夠:

  • 每一個類儘可能都有接口或抽象類,或者抽象類和接口二者都具有。

     這是依賴倒置的基本要求,接口和抽象類都是屬於抽象的,有了抽象纔可能依賴倒置。

  • 變量的顯示類型儘可能是接口或者是抽象類。

     不少書上說變量的類型必定要是接口或者是抽象類,這個有點絕對化了,好比一個工具類,xxxUtils通常是不須要接口或是抽象類的。還有,若是你要使用類的clone方法,就必須使用實現類,這個是JDK提供一個規範。

  • 任何類都不該該從具體類派生。

     若是一個項目處於開發狀態,確實不該該有從具體類派生出的子類的狀況,但這也不是絕對的,由於人都是會犯錯誤的,有時設計缺陷是在所不免的,所以只要不超過兩層的繼承都是能夠忍受的。特別是作項目維護的同志,基本上能夠不考慮這個規則,爲何?維護工做基本上都是作擴展開發,修復行爲,經過一個繼承關係,覆寫一個方法就能夠修正一個很大的Bug,何須再要去繼承最高的基類呢?

  • 儘可能不要覆寫基類的方法。

     若是基類是一個抽象類,並且這個方法已經實現了,子類儘可能不要覆寫。類間依賴的是抽象,覆寫了抽象方法,對依賴的穩定性會產生必定的影響。

  • 結合里氏替換原則使用

     在上一個章節中咱們講解了里氏替換原則,父類出現的地方子類就能出現,再結合本章節的講解,咱們能夠得出這樣一個通俗的規則: 接口負責定義public屬性和方法,而且聲明與其餘對象的依賴關係,抽象類負責公共構造部分的實現,實現類準確的實現業務邏輯,同時在適當的時候對父類進行細化。

     講了這麼多,估計你們對「倒置」這個詞仍是有點不理解,那到底什麼是「倒置」呢?咱們先說「正置」是什麼意思,依賴正置就是類間的依賴是實實在在的實現類間的依賴,也就是面向實現編程,這也是正常人的思惟方式,我要開奔馳車就依賴奔馳車,我要使用筆記本電腦就直接依賴筆記本電腦,而編寫程序須要的是對現實世界的事物進行抽象,抽象的結果就是有了抽象類和接口,而後咱們根據系統設計的須要產生了抽象間的依賴,代替了人們傳統思惟中的事物間的依賴,「倒置」就是從這裏產生的。

     依賴倒置原則的優勢在小型項目中很難體現出來,例如小於10我的月的項目,使用簡單的SSH架構,基本上不費太大力氣就能夠完成,是否採用依賴倒置原則影響不大。可是,在一個大中型項目中,採用依賴倒置原則能夠帶來很是多的優勢,特別是規避一些非技術因素引發的問題。項目越大,需求變化的機率也越大,經過採用依賴倒置原則設計的接口或抽象類對實現類進行約束,能夠減小需求變化引發的工做量劇增的狀況。人員的變更在大中型項目中也是時常存在的,若是設計優良、代碼結構清晰,人員變化對項目的影響基本爲零。大中型項目的維護週期通常都很長,採用依賴倒置原則可讓維護人員輕鬆地擴展和維護。

     依賴倒置原則是六個設計原則中最難以實現的原則,它是實現開閉原則的重要途徑,依賴倒置原則沒有實現,就別想實現對擴展開放,對修改關閉。在項目中,你們只要記住是「面向接口編程」就基本上抓住了依賴倒轉原則的核心。

     講了這麼多依賴倒置原則的優勢,咱們也來打擊一下你們,在現實世界中確實存在着必須依賴細節的事物,好比法律,就必須依賴細節的定義。 「殺人償命」在中國的法律中古今有之,那這裏的殺人就是一個抽象的含義,怎麼殺,殺什麼人,爲何殺人,都沒有定義,只要是殺人就通通得償命,那這就是有問題了,好人殺了壞人,還要陪上本身的一條性命,這是不公正的,從這一點看,咱們在實際的項目中使用依賴倒置原則時須要審時度勢,不要抓住一個原則不放,每個原則的優勢都是有限度的,並非放之四海而皆準的真理,因此別爲了遵循一個原則而放棄了一個項目的終極目標:投產上線和盈利。做爲一個項目經理或架構師,應該懂得技術只是實現目的的工具,惹惱了頂頭上司,設計作得再漂亮,代碼寫得再完美,項目作得再符合標準,一旦項目虧本,產品投入大於產出,那總體就是扯淡!你本身也別想混得更好!

相關文章
相關標籤/搜索