依賴倒置原則,英文縮寫DIP,全稱Dependence Inversion Principle。html
原始定義:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。spring
官方翻譯:高層模塊不該該依賴低層模塊,二者都應該依賴其抽象;抽象不該該依賴細節,細節應該依賴抽象。編程
上面的定義不難理解,主要包含兩次意思:設計模式
1)高層模塊不該該直接依賴於底層模塊的具體實現,而應該依賴於底層的抽象。換言之,模塊間的依賴是經過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的。api
2)接口和抽象類不該該依賴於實現類,而實現類依賴接口或抽象類。這一點其實不用多說,很好理解,「面向接口編程」思想正是這點的最好體現。架構
相比傳統的軟件設計架構,好比咱們常說的經典的三層架構,UI層依賴於BLL層,BLL層依賴於DAL層。因爲每一層都是依賴於下層的實現,這樣當某一層的結構發生變化時,它的上層就不得不也要發生改變,好比咱們DAL裏面邏輯發生了變化,可能會致使BLL和UI層都隨之發生變化,這種架構是很是荒謬的!好,這個時候若是咱們換一種設計思路,高層模塊不直接依賴低層的實現,而是依賴於低層模塊的抽象,具體表現爲咱們增長一個IBLL層,裏面定義業務邏輯的接口,UI層依賴於IBLL層,BLL層實現IBLL裏面的接口,因此具體的業務邏輯則定義在BLL裏面,這個時候若是咱們BLL裏面的邏輯發生變化,只要接口的行爲不變,上層UI裏面就不用發生任何變化。app
在經典的三層裏面,高層模塊直接依賴低層模塊的實現,當咱們將高層模塊依賴於底層模塊的抽象時,就好像依賴「倒置」了。這就是依賴倒置的由來。經過依賴倒置,可使得架構更加穩定、更加靈活、更好應對需求變化。框架
上面說了,在三層架構裏面增長一個接口層能實現依賴倒置,它的目的就是下降層與層之間的耦合,使得設計更加靈活。從這點上來講,依賴倒置原則也是「鬆耦合」設計的很好體現。ide
文章最開始的時候說了,依賴倒置是設計模式的設計原則之一,那麼在咱們那麼多的設計模式中,哪些設計模式遵循了依賴倒置的原則呢?這個就多了,好比咱們常見的工廠方法模式。下面博主就結合一個使用場景來講說依賴倒置原則如何可以使得設計更加靈活。函數
場景描述:還記得在一場風花雪月的邂逅:接口和抽象類這篇裏面介紹過設備的採集的例子,這篇繼續以這個使用場景來講明。設備有不少類型,每種設備都有登陸和採集兩個方法,經過DeviceService這個服務去啓動設備的採集,最開始咱們只有MML和TL2這兩種類型的設備,那麼來看看咱們的設計代碼。
代碼示例:
//MML類型的設備 public class DeviceMML { public void Login() { Console.WriteLine("MML設備登陸"); } public bool Spider() { Console.WriteLine("MML設備採集"); return true; } } //TL2類型設備 public class DeviceTL2 { public void Login() { Console.WriteLine("TL2設備登陸"); } public bool Spider() { Console.WriteLine("TL2設備採集"); return true; } } //設備採集的服務 public class DeviceService { private DeviceMML MML = null; private DeviceTL2 TL2 = null; private string m_type = null; //構造函數裏面經過類型來判斷是哪一種類型的設備 public DeviceService(string type) { m_type = type; if (type == "0") { MML = new DeviceMML(); } else if (type == "1") { TL2 = new DeviceTL2(); } } public void LoginDevice() { if (m_type == "0") { MML.Login(); } else if (m_type == "1") { TL2.Login(); } } public bool DeviceSpider() { if (m_type == "0") { return MML.Spider(); } else if (m_type == "1") { return TL2.Spider(); } else { return true; } } }
在Main函數裏面調用
class Program { static void Main(string[] args) { var oSpider = new DeviceService("1"); oSpider.LoginDevice(); var bRes = oSpider.DeviceSpider(); Console.ReadKey(); }
上述代碼通過開發、調試、部署、上線。能夠正常運行,貌似一切都OK。
日復一日、年復一年。後來公司又來兩種新的設備TELNET和TL5類型設備。因而程序猿們又有得忙了,加班,趕進度!因而代碼變成了這樣:
//MML類型的設備 public class DeviceMML { public void Login() { Console.WriteLine("MML設備登陸"); } public bool Spider() { Console.WriteLine("MML設備採集"); return true; } } //TL2類型設備 public class DeviceTL2 { public void Login() { Console.WriteLine("TL2設備登陸"); } public bool Spider() { Console.WriteLine("TL2設備採集"); return true; } } //TELNET類型設備 public class DeviceTELNET { public void Login() { Console.WriteLine("TELNET設備登陸"); } public bool Spider() { Console.WriteLine("TELNET設備採集"); return true; } } //TL5類型設備 public class DeviceTL5 { public void Login() { Console.WriteLine("TL5設備登陸"); } public bool Spider() { Console.WriteLine("TL5設備採集"); return true; } } //設備採集的服務 public class DeviceService { private DeviceMML MML = null; private DeviceTL2 TL2 = null; private DeviceTELNET TELNET = null; private DeviceTL5 TL5 = null; private string m_type = null; //構造函數裏面經過類型來判斷是哪一種類型的設備 public DeviceService(string type) { m_type = type; if (type == "0") { MML = new DeviceMML(); } else if (type == "1") { TL2 = new DeviceTL2(); } else if (type == "2") { TELNET = new DeviceTELNET(); } else if (type == "3") { TL5 = new DeviceTL5(); } } public void LoginDevice() { if (m_type == "0") { MML.Login(); } else if (m_type == "1") { TL2.Login(); } else if (m_type == "2") { TELNET.Login(); } else if (m_type == "3") { TL5.Login(); } } public bool DeviceSpider() { if (m_type == "0") { return MML.Spider(); } else if (m_type == "1") { return TL2.Spider(); } else if (m_type == "2") { return TELNET.Spider(); } else if (m_type == "3") { return TL5.Spider(); } else { return true; } } }
好比咱們想啓動TL5類型設備的採集,這樣調用能夠實現:
static void Main(string[] args) { var oSpider = new DeviceService("3"); oSpider.LoginDevice(); var bRes = oSpider.DeviceSpider(); Console.ReadKey(); }
花了九年二虎之力,總算是能夠實現了。但是又過了段時間,又有新的設備類型呢?是否是又要加班,又要改。這樣下去,感受這就是一個無底洞,再加上時間越久,項目所經歷的開發人員越容易發生變化,這個時候再改,那維護的成本堪比開發一個新的項目。而且,隨着設備類型的增多,代碼裏面充斥着大量的if...else,這樣的爛代碼簡直讓人沒法直視。
基於這種狀況,若是咱們當初設計這個系統的時候考慮了依賴倒置,那麼效果可能大相徑庭。咱們來看看依賴倒置如何解決以上問題的呢?
//定義一個統一接口用於依賴 public interface IDevice { void Login(); bool Spider(); } //MML類型的設備 public class DeviceMML : IDevice { public void Login() { Console.WriteLine("MML設備登陸"); } public bool Spider() { Console.WriteLine("MML設備採集"); return true; } } //TL2類型設備 public class DeviceTL2 : IDevice { public void Login() { Console.WriteLine("TL2設備登陸"); } public bool Spider() { Console.WriteLine("TL2設備採集"); return true; } } //TELNET類型設備 public class DeviceTELNET : IDevice { public void Login() { Console.WriteLine("TELNET設備登陸"); } public bool Spider() { Console.WriteLine("TELNET設備採集"); return true; } } //TL5類型設備 public class DeviceTL5 : IDevice { public void Login() { Console.WriteLine("TL5設備登陸"); } public bool Spider() { Console.WriteLine("TL5設備採集"); return true; } } //設備採集的服務 public class DeviceService { private IDevice m_device; public DeviceService(IDevice oDevice) { m_device = oDevice; } public void LoginDevice() { m_device.Login(); } public bool DeviceSpider() { return m_device.Spider(); } }
調用
static void Main(string[] args) { var oSpider = new DeviceService(new DeviceTL5()); oSpider.Login(); var bRes = oSpider.Spider(); Console.ReadKey(); }
代碼說明:上述解決方案中,咱們定義了一個IDevice接口,用於上層服務的依賴,也就是說,上層服務(這裏指DeviceService)僅僅依賴IDevice接口,對於具體的實現類咱們是無論的,只要接口的行爲不發生變化,增長新的設備類型後,上層服務不用作任何的修改。這樣設計下降了層與層之間的耦合,能很好地適應需求的變化,大大提升了代碼的可維護性。呵呵,看着是否是有點眼熟?是否是有點像某個設計模式?其實設計模式的設計原理正是基於此。
上面說了那麼多,都是在講依賴倒置的好處,那麼在咱們的項目中究竟如何具體實現和使用呢?
在介紹依賴倒置具體如何使用以前,咱們須要引入IOC容器相關的概念,咱們先來看看它們之間的關係。
依賴倒置原則(DIP):一種軟件架構設計的原則(抽象概念)。
控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式)。這是一個有點不太好理解和解釋的概念,通俗地說,就是應用程序自己不負責依賴對象的建立和維護,而是將它交給一個外部容器(好比Unity)來負責,這樣控制權就由應用程序轉移到了外部IoC 容器,即控制權實現了所謂的反轉。例如在類型A中須要使用類型B的實例,而B 實例的建立並不禁A 來負責,而是經過外部容器來建立。
依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)。也有不少博文裏面說IOC也叫DI,其實根據博主的理解,DI應該是IOC的具體實現方式,好比咱們如何實現控制反轉,答案就是經過依賴注入去實現。
IoC容器:依賴注入的框架,用來映射依賴,管理對象建立和生存週期(DI框架),自動建立、維護依賴對象。
關於Ioc容器,各個語言都有本身的成熟的解決方案,好比Java裏面最偉大的框架之一Spring,.net裏面輕量級的Autofac等。因爲博主對C#語言相對來講比較熟悉,這裏就結合C#裏面的一款ioc容器來舉例說明。.net裏面經常使用的ioc容器:
固然,還有其餘的IOC容器這裏就不一一列舉。下面博主仍是就Unity這種IOC容器來看看依賴倒置的具體實現。
Unity如何引入?咱們神奇的Nuget又派上用場了。最新的Unity版本已經到了4.0.1。
安裝成功後主要引入了三個dll。
UnityContainer.RegisterType<ITFrom,TTO>(); UnityContainer.RegisterType< ITFrom, TTO >(); UnityContainer.RegisterType< ITFrom, TTO >("keyName"); IEnumerable<T> databases = UnityContainer.ResolveAll<T>(); IT instance = UnityContainer.Resolve<IT>(); T instance = UnityContainer.Resolve<T>("keyName"); UnitContainer.RegisterInstance<T>("keyName",new T()); UnityContainer.BuildUp(existingInstance); IUnityContainer childContainer1 = parentContainer.CreateChildContainer();
仍然以上面的場景爲例說明,咱們注入DeviceMML這個實現類。
class Program { private static IUnityContainer container = null; static void Main(string[] args) { RegisterContainer(); var oSpider = container.Resolve<IDevice>(); oSpider.Login(); var bRes = oSpider.Spider(); Console.ReadKey(); } /// <summary> /// 代碼注入 /// </summary> public static void RegisterContainer() { container = new UnityContainer(); container.RegisterType<IDevice, DeviceMML>(); //默認註冊方式,若是後面再次默認註冊會覆蓋前面的 } }
運行結果
上面默認注入的方式中,咱們只能注入一種具體的實例,若是咱們須要同時注入多個類型的實例呢?看看咱們的 RegisterType() 方法有多個重載。
class Program { private static IUnityContainer container = null; static void Main(string[] args) { RegisterContainer(); var oSpider = container.Resolve<IDevice>("TL5"); oSpider.Login(); var bRes = oSpider.Spider(); Console.ReadKey(); } /// <summary> /// 代碼注入 /// </summary> public static void RegisterContainer() { container = new UnityContainer(); container.RegisterType<IDevice, DeviceMML>("MML"); //默認註冊(無命名),若是後面還有默認註冊會覆蓋前面的 container.RegisterType<IDevice, DeviceTELNET>("Telnet"); //命名註冊 container.RegisterType<IDevice, DeviceTL2>("TL2"); //命名註冊 container.RegisterType<IDevice, DeviceTL5>("TL5"); //命名註冊 } }
運行結果
在App.config或者Web.config裏面加入以下配置:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity> <!--容器--> <containers> <container name="Spider"> <!--映射關係--> <register type="ESTM.Spider.IDevice,ESTM.Spider" mapTo="ESTM.Spider.DeviceMML,ESTM.Spider" name="MML"></register> <register type="ESTM.Spider.IDevice,ESTM.Spider" mapTo="ESTM.Spider.DeviceTELNET,ESTM.Spider" name="TELNET"></register> <register type="ESTM.Spider.IDevice,ESTM.Spider" mapTo="ESTM.Spider.DeviceTL2,ESTM.Spider" name="TL2"></register> <register type="ESTM.Spider.IDevice,ESTM.Spider" mapTo="ESTM.Spider.DeviceTL5,ESTM.Spider" name="TL5"></register> </container> </containers> </unity> </configuration>
在代碼裏面註冊配置文件:
namespace ESTM.Spider { class Program { private static IUnityContainer container = null; static void Main(string[] args) { ContainerConfiguration(); var oSpider = container.Resolve<IDevice>("TL5"); oSpider.Login(); var bRes = oSpider.Spider(); Console.ReadKey(); } /// <summary> /// 配置文件注入 /// </summary> public static void ContainerConfiguration() { container = new UnityContainer(); UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); configuration.Configure(container, "Spider"); } } }
運行結果:
代碼說明
(1)
<register type="ESTM.Spider.IDevice,ESTM.Spider" mapTo="ESTM.Spider.DeviceMML,ESTM.Spider" name="MML"></register>
節點裏面,type對象抽象,mapTo對象具體實例對象,name對象實例的別名。
(2)在app.config裏面能夠配置多個 節點,不一樣的name配置不一樣的依賴對象。
(3)配置文件注入的靈活之處在於解耦。爲何這麼說呢?試想,若是咱們的IDevice接口對應着一個接口層,而DeviceMML、DeviceTELNET、DeviceTL二、DeviceTL5等實現類在另一個實現層裏面,咱們的UI層(這裏對應控制檯程序這一層)只須要添加IDevice接口層的引用,沒必要添加實現層的引用,經過配置文件注入,在運行的時候動態將實現類注入到UI層裏面來。這樣UI層就對實現層實現瞭解耦,實現層裏面的具體邏輯變化時,UI層裏面沒必要作任何更改。
到此,依賴倒置原則的講解基本結束了。根據博主的理解,設計模式的這些原則是設計模式的理論指導,而設計模式則是這些理論的具體運用。說一千道一萬,要想搞懂設計模式,必須先了解設計模式遵循的原則,不管是哪一種設計模式都會遵循一種或者多種原則。固然文章可能有理解不當的地方,歡迎大牛們指出。