控制反轉(zhuǎn)(Inversion of Control)是解決程序耦合問題的一種方案,還有種叫法是依賴注入(Dependency Injection),但我感覺Ioc(控制反轉(zhuǎn))是一種思想,DI(依賴注入)是實現(xiàn)這種思想的一種方式,或者說Ioc是一種概念,DI是這種概念的思想,不知道我這樣理解的對不對。可能一開始接觸這些東西有點莫名其妙,園友們寫的一些東西也看得頭疼,至少我當時是這樣,如果你是像我一樣的菜鳥,請跟我一起學習下,不看代碼,我們先看一個生活中的例子-壓水井和自來水廠的故事。
小時候在農(nóng)村喝水都是自家打井或是用電水泵取水,想什么時候喝就什么時候喝,想喝多少就喝多少,很方便,而且不用花錢。但是有個問題是,家里面的房子要裝修或是重建,原來打的井已經(jīng)不適合新建的房子了,也就是說需要重新打井,這就很麻煩,建多少次房子,需要打多少次的井(當然土豪才這樣)。
我們先看這個小示例,其實如果抽象一點的話,有點類似工廠模式,為什么?我們分析下:上面例子中的水可以看成一個產(chǎn)品,每家的井或是電水泵可以看成一個工廠,自己根據(jù)自家的情況來“生產(chǎn)”出來水,只有一家有井或是電水泵還好(其他家去他家取水,但不現(xiàn)實),如果每家都有一個井或是電水泵,就有點工廠泛濫的情況發(fā)生了,可能會出現(xiàn):
水污染:每家都吃不上水,這里面的水出現(xiàn)問題就是產(chǎn)品出現(xiàn)問題,這樣我們就需要在每個工廠里面進行處理,就比如需要在每家的井或電水泵上安裝一個凈水器,顯然代價比較大,也不太現(xiàn)實。
整體搬遷:原來的井或電水泵用不了了,每家的井或電水泵就需要重新搞,可能不太現(xiàn)實,當然只是做個假設,細想一下,這個問題的根源其實就是井或電水泵太多了,也就是工廠泛濫。
上面所說的問題為什么會出現(xiàn)?其實就是依賴關系作祟,每一家都要依賴自家的井或電水泵,也沒辦法,畢竟人要喝水,總不能跑到地下暗河去喝吧,只能通過井或電水泵(工廠)來取水(調(diào)用),這個問題在編程中就是依賴倒置原則的反例,何為依賴倒置原則:
高層次的模塊不應該依賴于低層次的模塊,他們都應該依賴于抽象。
抽象不應該依賴于具體,具體應該依賴于抽象。
第一點:高層次模塊(使用者)就是每戶人家,低層次模塊(被使用者)就是壓水井或電水泵,可以看出他們都是依賴于具體的對象,而并非依賴于抽象;第二點:水(抽象)依賴壓水井或電水泵(具體),人(具體)依賴壓水井(具體),而并非具體依賴于抽象。可以看出這兩點完全不設和依賴倒置原則,怎么解決問題呢?請看下面。
自來水廠
上面的示例中其實有個三個對象:每戶人家、壓水井或電水泵、水,就是在探討他們?nèi)齻€這之間的依賴關系,明確這一點很重要。
隨著時代的發(fā)展,壓水井和電水泵慢慢消失在我們的視野中(當然現(xiàn)在還有很多落后的地方在用,比如像我老家),政府就在每個村莊或是幾個村莊之間建設自來水廠,為什么政府要建設自來水廠?難道他們都是搞編程的?知道工廠泛濫的壞處?哈哈,我覺得應該是多收點錢吧,你覺得呢?開個玩笑。
不管政府目的如何,但好像解決了工廠泛濫的一些問題,我們再來分析下有了自來水廠會有什么不同,我們畫個示意圖看下:
畫的比較丑(莫笑),但是簡單的意思還是可以表達的,圖中的人和水都是抽象的,地下水和水庫依賴于于抽象的水,A村的人和B村的人依賴于抽象的人,人和水怎么關系呢?這個就有自來水廠決定了,它讓你喝地下水,你就不能喝水庫的水。這就基本符合依賴倒置原則:抽象不依賴于具體,具體依賴于抽象。
這中間關鍵在于自來水廠,沒了壓水井,有了自來水廠,我們看看上面壓水井的“工廠泛濫”問題能不能解決?
水污染:比如地下水出現(xiàn)問題,因為自來水廠不依賴地下水,而是依賴于抽象的水,地下水有問題,那我用水庫的水,水庫的水如果有問題,那我們用雨水凈化。。。我們?nèi)撕鹊降牟还苁裁此糠凑际撬?,不影響我們喝水就行了?/p>
整體搬遷:比如A村的人因為某些原因,要搬到B村,如果是上面壓水井的模式,幫過去就需要重新打井了,但是有了自來水廠,我只需要接個管線,按個水龍頭就行了,就這么簡單。
從上面的分析來看,建設自來水廠確實比壓水井可靠多了,回到我們這篇要講的正題-控制反轉(zhuǎn)(Ioc),你可能也有些明白了,其實自來水廠就可以看做是Ioc,用什么樣的水?給什么樣的人?都是自來水廠決定,好處就不用多說了,上面已經(jīng)講明,套用到編程里面是相同的道理,只可意會哦。
說到這里,你不禁有些驚訝,難道政府里面有系統(tǒng)架構(gòu)師?哈哈笑過。
上面的示例,下面我們再來用代碼復述一下,畢竟理論要結(jié)合實踐。
壓水井的問題-依賴
壓水井模式有三個對象:人、壓水井、水,我們就用常規(guī)的方式簡單寫下代碼:
1 ///2 /// 村民 3 /// 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 ///17 /// 壓水井 18 /// 19 public class PressWater 20 { 21 public UndergroundWater returnWater() 22 { 23 return new UndergroundWater(); 24 } 25 } 26 ///27 /// 地下水 28 /// 29 public class UndergroundWater 30 { 31 }
上面的代碼就是簡單演示村民通過壓水井喝水的過程,因為村民不能直接取得水,只能通過壓水井取得地下水,很明顯我們可以看出之間的依賴關系:
VillagePeople依賴于PressWater
VillagePeople依賴于UndergroundWater
PressWater依賴于UndergroundWater
我們在做業(yè)務處理的時候,簡單的依賴關系可以用上面的方式處理,如果太復雜的話就不行了,牽一發(fā)而動全身總歸不是很好。
大家可能說,上面不是講到“工廠泛濫”問題,這邊怎么沒指出?因為PressWater某一方面其實就可以看做一個小工廠,每家的壓水井不一樣,這邊只是說某一種,“工廠泛濫”其實就是依賴作祟,上面的例子說明的是依賴關系,一樣的道理,所以下面就用這個例子來做一些東西。
壓水井的問題解決-依賴倒置
我們在講壓水井的時候提到過依賴倒置原則,這邊就不再說了,因為VillagePeople依賴于PressWater、VillagePeople依賴于UndergroundWater、PressWater依賴于UndergroundWater,我們可以把PressWater(壓水井)和UndergroundWater(地下水)抽象出來,UndergroundWater屬于水的一種,可以抽象為IWater,PressWater因為是獲取水的方式之一,可以抽象為IWaterTool,這邊就要面向接口編程了,根據(jù)依賴倒置原則,我們把上面的代碼修改一下:
1 ///2 /// 村民 3 /// 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 ///17 /// 壓水井 18 /// 19 public class PressWater : IWaterTool 20 { 21 public IWater returnWater() 22 { 23 return new UndergroundWater(); 24 } 25 } 26 ///27 /// 獲取水方式接口 28 /// 29 public interface IWaterTool 30 { 31 IWater returnWater(); 32 } 33 ///34 /// 地下水 35 /// 36 public class UndergroundWater : IWater 37 { } 38 ///39 /// 水接口 40 /// 41 public interface IWater 42 { }
從上面的代碼可以看出,UndergroundWater依賴接口IWater,PressWater依賴IWaterTool和IWater,VillagePeople依賴IWaterTool和IWater,這樣就符合依賴倒置原則了,都是依賴于抽象,從而降低耦合度,這樣當一個方式變化了不會影響到其他,地下水污染了,我可以通過別的獲取工具獲取水,而不至于沒水喝。
但是上面說的忽略了個問題,接口總是會被實現(xiàn)的,也就是總會執(zhí)行:IWaterTool pw =newPressWater();這樣耦合度就產(chǎn)生了,也就是VillagePeople依賴于PressWater,我們可以通過工廠參數(shù)來產(chǎn)生不同的獲取工具對象,這種方式表面上雖然解決了問題,但是實質(zhì)上代碼耦合度并沒有改變,怎么辦呢?請接著往下看。
自來水廠-Ioc
通過Ioc模式可以徹底解決上面我們提到耦合的問題,它把耦合從代碼中移出去,放到統(tǒng)一的XML文件中,通過一個容器在需要的時候把這個依賴關系形成,即把需要的接口實現(xiàn)注入到需要它的類中。就像自來水廠一樣,水的來源、水的去處都是它來決定,人們只要通過它來喝水就行了,而不需要考慮的太多。
早在微軟提供的一個示例框架PetShop中就有Ioc的體現(xiàn),只不過那時候不太懂,PetShop是通過反射創(chuàng)建對象,上面的代碼我們修改一下:
1 ///2 /// 村民 3 /// 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模式,系統(tǒng)中通過引入實現(xiàn)了Ioc模式的Ioc容器,即可由Ioc容器來管理對象的生命周期、依賴關系等,從而使得應用程序的配置和依賴性規(guī)范與實際的應用程序代碼分開。其中一個特點就是通過文本的配置文件進行應用程序組件間相互關系的配置,而不用重新修改并編譯具體的代碼。
看到這里,是不是感覺Ioc模式有點“熱插拔”的意思?有點像USB一樣呢?
自來水廠運行-DI
如果把自來水廠看做Ioc,那我覺得依賴注入(DI)就是這個自來水廠的運行模式,當然其實是一個意思,依賴注入是什么?全稱Dependency Injection,我們從字面上理解下:需要的接口實現(xiàn)注入到需要它的類中,這就是依賴注入的意思。自來水廠獲取水源的時候,控制這個獲取水源的開關可以看做是依賴注入的一種體現(xiàn),話不多說,懂得就好。
依賴注入的方式有很多,就像控制獲取水源的開關有很多一樣。
構(gòu)造器注入(Constructor Injection):Ioc容器會智能地選擇選擇和調(diào)用適合的構(gòu)造函數(shù)以創(chuàng)建依賴的對象。如果被選擇的構(gòu)造函數(shù)具有相應的參數(shù),Ioc容器在調(diào)用構(gòu)造函數(shù)之前解析注冊的依賴關系并自行獲得相應參數(shù)對象;
屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創(chuàng)建之后,Ioc容器會自動初始化該屬性;
方法注入(Method Injection):如果被依賴對象需要調(diào)用某個方法進行相應的初始化,在該對象創(chuàng)建之后,Ioc容器會自動調(diào)用該方法。
有時間可以好好研究下依賴注入的各種方式,這邊我們就使用微軟提供的Unity實現(xiàn)依賴注入,方式是構(gòu)造器注入,首先使用Nuget工具將Unity添加到項目中,安裝Unity需要.net framework4.5支持。
添加完之后,發(fā)下項目中多了Microsoft.Practices.Unity和Microsoft.Practices.Configuation兩個dll,代碼如下:
1 ///2 /// 人接口 3 /// 4 public interface IPeople 5 { 6 void DrinkWater(); 7 } 8 ///9 /// 村民 10 /// 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 }
調(diào)用代碼:
1 static void Main(string[] args) 2 { 3 UnityContainer container = new UnityContainer(); 4 container.RegisterType(); 5 TestFour.IPeople people = container.Resolve (); 6 people.DrinkWater(); 7 }
首先我們創(chuàng)建一個Unity容器,接下來我們需要在容器中注冊一種類型,它是一個類型的映射,接口類型是IWaterTool,返回類型為PressWater,這個過程中就是要告訴容易我要注冊的類型。
比如自來水廠要用地下水作為水源,這時候操作員輸入命令,就是RegisterType,參數(shù)為IWaterTool、PressWater,下面就是調(diào)用Resolve生成對象,這個過程表示要把水輸送到哪戶人家,命令是Resolve,參數(shù)為VillagePeople,接下來就是直接打開水龍頭喝水了,很方便吧。
關于依賴注入其實有很多的東西,上面的示例只是拋磚引玉,有時間的話好好研究下,比如依賴注入的其他方式等等。
編輯:hfy
-
IOC
+關注
關注
0文章
28瀏覽量
10096 -
Unity
+關注
關注
1文章
127瀏覽量
21776
發(fā)布評論請先 登錄
相關推薦
評論