小菜學習設計模式(五)—控制反轉(Ioc)

寫在前面

設計模式目錄:html

本篇目錄:編程

  好長時間沒有更新設計模式系列了,不是不想寫,奈何小菜功力有限,這段時間也在給本身充電,畢竟路要一步一步走,急不得。url

  控制反轉(Inversion of Control)是解決程序耦合問題的一種方案,還有種叫法是依賴注入(Dependency Injection),但我感受Ioc控制反轉)是一種思想,DI(依賴注入)是實現這種思想的一種方式,或者說Ioc是一種概念,DI是這種概念的思想,不知道我這樣理解的對不對。可能一開始接觸這些東西有點莫名其妙,園友們寫的一些東西也看得頭疼,至少我當時是這樣,若是你是像我同樣的菜鳥,請跟我一塊兒學習下,不看代碼,咱們先看一個生活中的例子-壓水井和自來水廠的故事

  內容有點多,請堅持往下看哦!

壓水井

  小時候在農村喝水都是自家打井或是用電水泵取水,想何時喝就何時喝,想喝多少就喝多少,很方便,並且不用花錢。可是有個問題是,家裏面的房子要裝修或是重建,原來打的井已經不適合新建的房子了,也就是說須要從新打井,這就很麻煩,建多少次房子,須要打多少次的井(固然土豪才這樣)。

  咱們先看這個小示例,其實若是抽象一點的話,有點相似工廠模式,爲何?咱們分析下:上面例子中的水能夠當作一個產品,每家的井或是電水泵能夠當作一個工廠,本身根據自家的狀況來「生產」出來水,只有一家有井或是電水泵還好(其餘家去他家取水,但不現實),若是每家都有一個井或是電水泵,就有點工廠氾濫的狀況發生了,可能會出現:

  • 水污染:每家都吃不上水,這裏面的水出現問題就是產品出現問題,這樣咱們就須要在每一個工廠裏面進行處理,就好比須要在每家的井或電水泵上安裝一個淨水器,顯然代價比較大,也不太現實。
  • 總體搬遷:原來的井或電水泵用不了了,每家的井或電水泵就須要從新搞,可能不太現實,固然只是作個假設,細想一下,這個問題的根源其實就是井或電水泵太多了,也就是工廠氾濫。

  上面所說的問題爲何會出現?其實就是依賴關係做祟,每一家都要依賴自家的井或電水泵,也沒辦法,畢竟人要喝水,總不能跑到地下暗河去喝吧,只能經過井或電水泵(工廠)來取水(調用),這個問題在編程中就是依賴倒置原則的反例,何爲依賴倒置原則

  • 高層次的模塊不該該依賴於低層次的模塊,他們都應該依賴於抽象。
  • 抽象不該該依賴於具體,具體應該依賴於抽象。

  第一點:高層次模塊(使用者)就是每戶人家,低層次模塊(被使用者)就是壓水井或電水泵,能夠看出他們都是依賴於具體的對象,而並不是依賴於抽象;第二點:水(抽象)依賴壓水井或電水泵(具體),人(具體)依賴壓水井(具體),而並不是具體依賴於抽象。能夠看出這兩點徹底不設和依賴倒置原則,怎麼解決問題呢?請看下面。

自來水廠

  上面的示例中其實有個三個對象:每戶人家、壓水井或電水泵、水,就是在探討他們三個這之間的依賴關係,明確這一點很重要。

  隨着時代的發展,壓水井和電水泵慢慢消失在咱們的視野中(固然如今還有不少落後的地方在用,好比像我老家),政府就在每一個村莊或是幾個村莊之間建設自來水廠,爲何政府要建設自來水廠?難道他們都是搞編程的?知道工廠氾濫的壞處?哈哈,我以爲應該是多收點錢吧,你以爲呢?開個玩笑。

  無論政府目的如何,但好像解決了工廠氾濫的一些問題,咱們再來分析下有了自來水廠會有什麼不一樣,咱們畫個示意圖看下:

  畫的比較醜(莫笑),可是簡單的意思仍是能夠表達的,圖中的人和水都是抽象的,地下水和水庫依賴於於抽象的水,A村的人和B村的人依賴於抽象的人,人和水怎麼關係呢?這個就有自來水廠決定了,它讓你喝地下水,你就不能喝水庫的水。這就基本符合依賴倒置原則抽象不依賴於具體,具體依賴於抽象

  這中間關鍵在於自來水廠,沒了壓水井,有了自來水廠,咱們看看上面壓水井的「工廠氾濫」問題能不能解決?

  • 水污染:好比地下水出現問題,由於自來水廠不依賴地下水,而是依賴於抽象的水,地下水有問題,那我用水庫的水,水庫的水若是有問題,那咱們用雨水淨化。。。咱們人喝到的無論什麼水?反正都是水,不影響咱們喝水就好了。
  • 總體搬遷:好比A村的人由於某些緣由,要搬到B村,若是是上面壓水井的模式,幫過去就須要從新打井了,可是有了自來水廠,我只須要接個管線,按個水龍頭就好了,就這麼簡單。

  從上面的分析來看,建設自來水廠確實比壓水井可靠多了,回到咱們這篇要講的正題-控制反轉(Ioc),你可能也有些明白了,其實自來水廠就能夠看作是Ioc,用什麼樣的水?給什麼樣的人?都是自來水廠決定,好處就不用多說了,上面已經講明,套用到編程裏面是相同的道理,只可意會哦。

  說到這裏,你不由有些驚訝,難道政府裏面有系統架構師?哈哈笑過。

  上面的示例,下面咱們再來用代碼複述一下,畢竟理論要結合實踐。

壓水井的問題-依賴

  壓水井模式有三個對象:人、壓水井、水,咱們就用常規的方式簡單寫下代碼:

 1         /// <summary>
 2         /// 村民
 3         /// </summary>
 4         public class VillagePeople
 5         {
 6             public void DrinkWater()
 7             {
 8                 PressWater pw = new PressWater();
 9                 UndergroundWater uw = pw.returnWater();
10                 if (uw!=null)
11                 {
12                     Console.WriteLine("地下水好甜啊!!!");
13                 }
14             }
15         }
16         /// <summary>
17         /// 壓水井
18         /// </summary>
19         public class PressWater
20         {
21             public UndergroundWater returnWater()
22             {
23                 return new UndergroundWater();
24             }
25         }
26         /// <summary>
27         /// 地下水
28         /// </summary>
29         public class UndergroundWater
30         { 
31         }

  上面的代碼就是簡單演示村民經過壓水井喝水的過程,由於村民不能直接取得水,只能經過壓水井取得地下水,很明顯咱們能夠看出之間的依賴關係:

  • VillagePeople依賴於PressWater
  • VillagePeople依賴於UndergroundWater
  • PressWater依賴於UndergroundWater

  咱們在作業務處理的時候,簡單的依賴關係能夠用上面的方式處理,若是太複雜的話就不行了,牽一髮而動全身總歸不是很好。

  你們可能說,上面不是講到「工廠氾濫」問題,這邊怎麼沒指出?由於PressWater某一方面其實就能夠看作一個小工廠,每家的壓水井不同,這邊只是說某一種,「工廠氾濫」其實就是依賴做祟,上面的例子說明的是依賴關係,同樣的道理,因此下面就用這個例子來作一些東西。

壓水井的問題解決-依賴倒置

  咱們在講壓水井的時候提到過依賴倒置原則,這邊就再也不說了,由於VillagePeople依賴於PressWaterVillagePeople依賴於UndergroundWater、PressWater依賴於UndergroundWater,咱們能夠把PressWater(壓水井)UndergroundWater(地下水)抽象出來,UndergroundWater屬於水的一種,能夠抽象爲IWaterPressWater由於是獲取水的方式之一,能夠抽象爲IWaterTool,這邊就要面向接口編程了,根據依賴倒置原則,咱們把上面的代碼修改一下:

 1         /// <summary>
 2         /// 村民
 3         /// </summary>
 4         public class VillagePeople
 5         {
 6             public void DrinkWater()
 7             {
 8                 IWaterTool pw = new PressWater();
 9                 IWater uw = pw.returnWater();
10                 if (uw != null)
11                 {
12                     Console.WriteLine("水好甜啊!!!");
13                 }
14             }
15         }
16         /// <summary>
17         /// 壓水井
18         /// </summary>
19         public class PressWater : IWaterTool
20         {
21             public IWater returnWater()
22             {
23                 return new UndergroundWater();
24             }
25         }
26         /// <summary>
27         /// 獲取水方式接口
28         /// </summary>
29         public interface IWaterTool
30         {
31             IWater returnWater();
32         }
33         /// <summary>
34         /// 地下水
35         /// </summary>
36         public class UndergroundWater : IWater
37         { }
38         /// <summary>
39         /// 水接口
40         /// </summary>
41         public interface IWater
42         { }

  從上面的代碼能夠看出,UndergroundWater依賴接口IWater,PressWater依賴IWaterTool和IWater,VillagePeople依賴IWaterTool和IWater,這樣就符合依賴倒置原則了,都是依賴於抽象,從而下降耦合度,這樣當一個方式變化了不會影響到其餘,地下水污染了,我能夠經過別的獲取工具獲取水,而不至於沒水喝。

  可是上面說的忽略了個問題,接口老是會被實現的,也就是總會執行:IWaterTool pw = new PressWater();這樣耦合度就產生了,也就是VillagePeople依賴於PressWater,咱們能夠經過工廠參數來產生不一樣的獲取工具對象,這種方式表面上雖然解決了問題,可是實質上代碼耦合度並無改變,怎麼辦呢?請接着往下看。

自來水廠-Ioc

  經過Ioc模式能夠完全解決上面咱們提到耦合的問題,它把耦合從代碼中移出去,放到統一的XML文件中,經過一個容器在須要的時候把這個依賴關係造成,即把須要的接口實現注入到須要它的類中。就像自來水廠同樣,水的來源、水的去處都是它來決定,人們只要經過它來喝水就好了,而不須要考慮的太多。

  早在微軟提供的一個示例框架PetShop中就有Ioc的體現,只不過那時候不太懂,PetShop是經過反射建立對象,上面的代碼咱們修改一下:

 1         /// <summary>
 2         /// 村民
 3         /// </summary>
 4         public class VillagePeople
 5         {
 6             public void DrinkWater()
 7             {
 8                 IWaterTool pw = (IWaterTool)Assembly.Load(ConfigurationManager.AppSettings["AssemName"]).CreateInstance(ConfigurationManager.AppSettings["WaterToolName"]);
 9                 IWater uw = pw.returnWater();
10                 if (uw != null)
11                 {
12                     Console.WriteLine("水好甜啊!!!");
13                 }
14             }
15         }

  上面代碼中咱們只須要在配置文件中添加獲取水工具的名稱WaterToolName就好了,由於一種工具對應獲取特定的一種水,因此水的種類不須要配置。地下水污染了,咱們只須要在配置文件中修改一下WaterToolName就能夠了。

  Ioc模式,系統中經過引入實現了Ioc模式的Ioc容器,便可由Ioc容器來管理對象的生命週期、依賴關係等,從而使得應用程序的配置和依賴性規範與實際的應用程序代碼分開。其中一個特色就是經過文本的配置文件進行應用程序組件間相互關係的配置,而不用從新修改並編譯具體的代碼。

  看到這裏,是否是感受Ioc模式有點「熱插拔」的意思?有點像USB同樣呢?

自來水廠運行-DI

  若是把自來水廠看作Ioc,那我以爲依賴注入(DI)就是這個自來水廠的運行模式,固然實際上是一個意思,依賴注入是什麼?全稱Dependency Injection,咱們從字面上理解下:須要的接口實現注入到須要它的類中,這就是依賴注入的意思。自來水廠獲取水源的時候,控制這個獲取水源的開關能夠看作是依賴注入的一種體現,話很少說,懂得就好。

  依賴注入的方式有不少,就像控制獲取水源的開關有不少同樣。

  • 構造器注入(Constructor Injection):Ioc容器會智能地選擇選擇和調用適合的構造函數以建立依賴的對象。若是被選擇的構造函數具備相應的參數,Ioc容器在調用構造函數以前解析註冊的依賴關係並自行得到相應參數對象;
  • 屬性注入(Property Injection):若是須要使用到被依賴對象的某個屬性,在被依賴對象被建立以後,Ioc容器會自動初始化該屬性;
  • 方法注入(Method Injection):若是被依賴對象須要調用某個方法進行相應的初始化,在該對象建立以後,Ioc容器會自動調用該方法。

  有時間能夠好好研究下依賴注入的各類方式,這邊咱們就使用微軟提供的Unity實現依賴注入,方式是構造器注入,首先使用Nuget工具將Unity添加到項目中,安裝Unity須要.net framework4.5支持。

  添加完以後,發下項目中多了Microsoft.Practices.Unity和Microsoft.Practices.Configuation兩個dll,代碼以下:

 1         /// <summary>
 2         /// 人接口
 3         /// </summary>
 4         public interface IPeople
 5         {
 6             void DrinkWater();
 7         }
 8         /// <summary>
 9         /// 村民
10         /// </summary>
11         public class VillagePeople : IPeople
12         {
13             IWaterTool _pw;
14             public VillagePeople(IWaterTool pw)
15             {
16                 _pw = pw;
17             }
18             public void DrinkWater()
19             {
20                 IWater uw = _pw.returnWater();
21                 if (uw != null)
22                 {
23                     Console.WriteLine("水好甜啊!!!");
24                 }
25             }
26         }

  調用代碼:

1         static void Main(string[] args)
2         {
3             UnityContainer container = new UnityContainer();
4             container.RegisterType<TestFour.IWaterTool, TestFour.PressWater>();
5             TestFour.IPeople people = container.Resolve<TestFour.VillagePeople>();
6             people.DrinkWater();
7         }

  首先咱們建立一個Unity容器,接下來咱們須要在容器中註冊一種類型,它是一個類型的映射,接口類型是IWaterTool,返回類型爲PressWater,這個過程當中就是要告訴容易我要註冊的類型。

  好比自來水廠要用地下水做爲水源,這時候操做員輸入命令,就是RegisterType,參數爲IWaterTool、PressWater,下面就是調用Resolve生成對象,這個過程表示要把水輸送到哪戶人家,命令是Resolve,參數爲VillagePeople,接下來就是直接打開水龍頭喝水了,很方便吧。

  關於依賴注入其實有不少的東西,上面的示例只是拋磚引玉,有時間的話好好研究下,好比依賴注入的其餘方式等等。

後記

  上面的示例代碼沒多少東西,若是有感興趣的朋友能夠下載看看,地址:http://pan.baidu.com/s/1ntK61wl

  這篇寫完,關於建立對象型模式就差很少了,接下來就是構建複雜結構型模式了,學習這些東西,都是爲了寫出更好的代碼,固然還有很長的路要走,革命還沒有成功,同志仍需努力,也但願你與我共勉。

  若是你以爲本篇文章對你有所幫助,請點擊右下部「推薦」,^_^

相關文章
相關標籤/搜索