定義:
高層次的模塊不應該依賴於低層次的模塊,兩者都應該依賴於抽象接口;抽象接口不應該依賴於具體實現。而具體實現則應該依賴於抽象接口。依賴倒置原則英文全稱為Dependence Inversion Principle,簡稱為DIP。
問題由來:
類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責復雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
解決方案:
將類A修改為依賴接口I,類B和類C各自實現接口I,類A通過接口I間接與類B或者類C發生聯系,則會大大降低修改類A的幾率。
采用依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,減少並行開發引起的風險,提高代碼的可讀性和可維護性。依賴倒置原則的核心就是面向接口編程,理解了面向接口編程也就理解了依賴倒置原則。下面通過一個司機開車的例子簡單說明一下:
有一個奔馳車類,奔馳車可以運行,方法為Run,司機類有一個開奔馳車的方法Drive。司機和奔馳車類的代碼如下所示:
///<summary> /// 奔馳車類 ///</summary> public class Benz { //奔馳車運行 public void Run() { Console.WriteLine("奔馳車開始運行..."); } } ///<summary> /// 司機類 ///</summary> public class Driver { public void Drive(Benz benz) { benz.Run(); } }
主函數司機小明開動奔馳車,代碼如下:
class Client { static void Main(string[] args) { //一輛奔馳車 var benz = new Benz(); //司機小明 var xiaoming = new Driver(); //小明開奔馳 xiaoming.Drive(benz); Console.ReadKey(); } }
上面實現了司機開奔馳車的場景,但是對於實際的業務來說,需求是不斷變化的。在技術上"變更才顯真功夫",只有在"變化"過程中才能知道自己設計或程序是否是松耦合。上面的例子,我們加點要求:司機也要會開寶馬車。要完成這個必須先有個寶馬車類,如下所示:
///<summary> /// 寶馬車類 ///</summary> public class Bmw { //寶馬車運行 public void Run() { Console.WriteLine("寶馬車開始運行..."); } }
盡管有了寶馬車類,但是司機根本木有開寶馬車的方法。你可能會說,木有那就加上開寶馬車的方法!這樣,是解決了一時的問題,但是還有法拉利、賓利等車呢?因此,是我們的設計出現了問題:司機類和奔馳車類之間是一個緊耦合的關系,其導致的結果就是系統的可維護性大大降低,可讀性降低,兩個相似的類需要閱讀兩個文件,這顯然不可取。還有穩定性:固化的、健壯的才是穩定的。這裡只是增加了一個車類就需要修改司機類,這不是穩定性,這是易變性。被依賴者的變更竟然讓依賴者來承擔修改的成本,這樣的依賴關系誰肯承擔!
在實際項目的開發中,要盡可能減少並行開發引起的風險。並行開發最大的風險就是風險擴散,本來只是一段程序的錯誤或異常,逐步波及一個功能,一個模塊,甚至到最後毀壞了整個項目。因為一個團隊,幾十人甚至上百人人開發,各人負責不同的功能模塊,甲負責汽車類的建造,乙負責司機類的建造,在甲沒有完成的情況下,乙是不能完全地編寫代碼的,缺少汽車類,編譯器根本就不會讓你通過!在缺少Benz類的情況下,Driver類能編譯嗎?更不要說是單元測試了!這種相互依存的關系在實際開發中是不被允許的,另一個角度來說這樣開發只能挨個進行修改,並且每一項修改可能牽一發而動全身。這種模式 在現在的大中型項目中已經是完全不能勝任了,一個項目是一個團隊的協作結果,一個人不可能了解所有的業務和所有的技術,要協作就要並行開發,要並行開發就要解決模塊之間的項目依賴關系,然後依賴倒置原則就隆重出場了,呵呵。
依賴倒置原則的核心就是接口,下面我們為車和司機定義兩個接口ICar和IDriver,代碼如下所示:
///<summary> /// 汽車接口 ///</summary> public interface ICar { void Run(); } ///<summary> /// 司機接口 ///</summary> public interface IDriver { void Drive(ICar car); }
然後讓上面定義的司機和汽車(寶馬和奔馳)各自繼承自己對應的接口,代碼如下所示:
///<summary> /// 奔馳車類 ///</summary> public class Benz:ICar { //奔馳車運行 public void Run() { Console.WriteLine("奔馳車開始運行..."); } } ///<summary> /// 寶馬車類 ///</summary> public class Bmw:ICar { //寶馬車運行 public void Run() { Console.WriteLine("寶馬車開始運行..."); } } ///<summary> /// 司機類 ///</summary> public class Driver:IDriver { public void Drive(ICar car) { car.Run(); } }
此時的類的結構圖如下所示:
在開發實現業務需求時,我們應該謹記抽象不依賴細節。在這裡,汽車和司機的接口都不依賴細節(具體的實現類),看到這應該對依賴倒置原則理解的差不多了吧。
依賴倒置原則是一個指導思想,是通過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的松耦合,為更好使用此原則,在實際項目中我們要按如下方法使用:
依賴倒置原則是六個設計原則中最難以實現的原則,它是實現開閉原則的重要途徑,依賴倒置原則沒有實現,就別想實現對擴展開放,對修改關閉。在項目中,大家只要記住是"面向接口編程"就基本上抓住了依賴倒轉原則的核心。順便說一下,實際的項目投產上線和盈利是第一要務,因此設計模式的原則只是提供了指導思想,我們不應該主動去違背,但是限於實際情況不得不違背,否則即便設計得有多麼好,架構多麼完美,這都是扯淡的事情。一旦超過預期工期或者項目虧本,你老板不高興,然後你也會不高興的…
同意樓上,像這種簡單的組合關系就是依賴的一種體現。所謂依賴倒置,就是說,依賴的B不是直接通過B b=new B();來獲取,而是先在工廠裡先new好,A什麼時候需要用的時候直接去工廠裡取就是了。
面向對象三要素
封裝(Encapsulation)
繼承(Inheritance)
多態(Polymorphism)
面向對象五原則
單一職責原則(SRP)
開放-封閉原則(OCP)
Liskov替換原則(LSP)
依賴倒置原則(DIP)
接口隔離原則(ISP)
面向對象六視點
復用(Reusibility)
擴展(Extensibility)
分離(Separability)
變化(Change)
簡約(Simplicity)
一致(Coherance)
應該就這些吧。有問題,可以接著問。