小菜學習設計模式(五)—控制反轉(Ioc)
寫在前面
設計模式目錄:
本篇目錄:
好長時間沒有更新設計模式系列了,不是不想寫,奈何小菜功力有限,這段時間也在給自己充電,畢竟路要一步一步走,急不得。
控制反轉(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依賴於PressWater、VillagePeople依賴於UndergroundWater、PressWater依賴於UndergroundWater,我們可以把PressWater(壓水井)和UndergroundWater(地下水)抽象出來,UndergroundWater屬於水的一種,可以抽象為IWater,PressWater因為是獲取水的方式之一,可以抽象為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,接下來就是直接開啟水龍頭喝水了,很方便吧。
關於依賴注入其實有很多的東西,上面的示例只是拋磚引玉,有時間的話好好研究下,比如依賴注入的其他方式等等。
後記
這篇寫完,關於建立物件型模式就差不多了,接下來就是構建複雜結構型模式了,學習這些東西,都是為了寫出更好的程式碼,當然還有很長的路要走,革命尚未成功,同志仍需努力,也希望你與我共勉。
如果你覺得本篇文章對你有所幫助,請點選右下部“推薦”,^_^
相關推薦
控制反轉IOC的依賴注入方式 【調侃】IOC前世今生 IoC模式 談談對Spring IOC的理解 一個簡單的小程式演示Unity的三種依賴注入方式 小菜學習設計模式(五)—控制反轉(Ioc) IoC模式(依賴、依賴倒置、依賴注入、控制反轉) IoC模式
轉自:https://www.cnblogs.com/ysyn/p/5563256.html 引言: 專案中遇到關於IOC的一些內容,因為和正常的邏輯程式碼比較起來,IOC有點反常。因此本文記錄IOC的一些基礎知識,並附有相應的簡單例項,而在實際專案中再複雜的應用也只是在
小菜學習設計模式(五)—控制反轉(Ioc)
寫在前面 設計模式目錄: 本篇目錄: 好長時間沒有更新設計模式系列了,不是不想寫,奈何小菜功力有限,這段時間也在給自己充電,畢竟路要一步一步走,急不得。 控制反轉(Inversion of Control)是解決程式耦合問題的一種方案,還有種叫法是依賴注入(Dependency Inj
小菜學習設計模式(四)—原型(Prototype)模式
1 /// <summary> 2 /// 心臟類 3 /// </summary> 4 public class Heart 5 { 6 private int _size; 7 p
小菜學習設計模式(三)—工廠方法(Factory Method)模式
前言 設計模式目錄: 本篇目錄: 其實大家都知道,在工廠方法(Factory Method)模式之前有個簡單工廠模式,也就是靜態工廠方法(Static Factory Method)模式,在簡單工廠模式之前有個我們都熟悉的三層架構模式,那我們就上到下一層一層的來了解下。 三層架構 三
小菜學習設計模式(一)—模板方法(Template)模式
1 /// <summary> 2 /// 資料庫操作類 3 /// </summary> 4 public class DbHelperOra 5 { 6 public static bool Quer
小菜學習設計模式(二)—單例(Singleton)模式
前言 設計模式目錄: 本篇目錄: 簡單實現 執行緒安全 後記 單例模式(Singleton)可以說是最簡單的模式,對.net來說,因為不需要考慮到垃圾回收機制,實現起來很簡單,但是對於沒有提供記憶體管理的平臺來說,比如C++,因為單例模式只考慮建立物件,所以
複習之spring基礎(一)——控制反轉(ioc)和依賴注入(di)
Spring的核心——控制反轉 ioc: 反轉控制. 建立物件的方式反轉了.從我們自己建立物件,反轉給spring(程式)來建立. 實現ioc思想需要,di做支援 di: 依賴注入.將必須的屬性注入到物件當中.是實現ioc思想必須條件. 三種物件建立方式 無參
跟著實例學習設計模式(3)-工廠方法(創建型)
迪米特 tex 新的 類的設計 package set pre sdn sso 工廠方法屬於創建型設計模式。 設計意圖:定義一個用於創建對象的接口。讓子類決定實例化哪一個類,工廠方法使一個類的實例化延遲到其子類。 靜態工廠使用面向對象的方式,有
C#設計模式之五創建者模式(Builder)【創建型】
包含 direct linq 自然 解釋 並且 宋體 主板 但是 一、引言 今天我們要講講Builder模式,也就是建造者模式,當然也有叫生成器模式的。在現實生活中,我們經常會遇到一些構成比較復雜的物品,比如:電腦,它就是一個復雜的物品,它主要是由CPU、主板、硬盤、顯卡
C#設計模式之五原型模式(Prototype Pattern)【創建型】
tom method 權限 type() 技術 cto 具體類 方便 logs 原文:C#設計模式之五原型模式(Prototype Pattern)【創建型】一、引言 在開始今天的文章之前先說明一點,歡迎大家來指正。很多人說原型設計模式會節省機器內存,他們說是拷貝
Java學習--設計模式之結構型模式(二)
and 它的 null spa bubuko imp AD mco flyweight 一、裝飾器模式(Decorator Pattern) 1、概念 裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類
設計模式大雜燴(24種設計模式的總結以及學習設計模式的幾點建議)
作者:zuoxiaolong8810(左瀟龍),轉載請註明出處,特別說明:本博文來自博主原部落格,為保證新部落格中博文的完整性,特複製到此留存,如需轉載請註明新部落格地址即可。 迄今為止
設計模式 ( 十五 ) 觀察者模式Observer(物件行為型)
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
java設計模式十五(觀察者模式)
java設計模式—觀察者模式 一.概述 在物件之間定義了一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件會收到通知並自動更新。這有點像我們的maven,比如maven的pom.xml刪除某個依賴包時,eclipse裡的程式碼就會做出相應的錯誤提示
從JavaScript學習設計模式(Iterator)
ES6增加了Map,Set資料結構而且還實現了迭代器機制(Iterator),這是多麼令人興奮的事情。 迭代器(Iterator)模式,又叫做遊標(Cursor)模式。GOF給出的定義為:提
從原始碼中學習設計模式系列——單例模式序/反序列化以及反射攻擊的問題(二)
一、前言 這篇文章是學習單例模式的第二篇,之前的文章一下子就給出來看起來很高大上的實現方法,但是這種模式還是存在漏洞的,具體有什麼問題,大家可以停頓一會兒,思考一下。好了,不賣關子了,下面我們來看看每種單例模式存在的問題以及解決辦法。 二、每種Singleton 模式的演進 模式一
【原】從頭學習設計模式(一)——單例模式
一、引入 單例模式作為23種設計模式中的最基礎的一種模式,在平時開發中應用也非常普遍。到底哪些類應該設計成單例的呢,我們來舉個最通俗的例子。在一個父容器中單擊某個選單項開啟一個子視窗,如果不使用單例又沒有作選單項的可用控制的話,每次單擊選單項都會開啟一個新視窗。這不僅會浪費記憶體資源,在程式邏輯
Java學習筆記——設計模式之五.工廠方法
strong scanner multipl 石頭 simple 決定 定義 opera 下使用 水邊一只青蛙在笑 ——石頭和水 工廠方法模式(Factory Method),定義了一個用於創建對象的接口,讓實現類決定實例化哪一個類。工廠方法使一個類的實
Javascript設計模式與開發實踐詳解(二:策略模式) http://www.jianshu.com/p/ef53781f6ef2
的人 思想 ram gis pan pro msg have 改變 上一章我們介紹了單例模式及JavaScript惰性單例模式應用這一次我主要介紹策略模式策略模式是定義一系列的算法,把它們一個個封裝起來,並且讓他們可以互相替換。比方說在現實中很多時候也有很多途徑到達同一個
設計模式的征途—14.職責鏈(Chain of Responsibility)模式
and 問題 調試 決定 tor 客戶端 edi toa 發送 相信大家都玩過類似於“鬥地主”的紙牌遊戲,某人出牌給他的下家,下家看看手中的牌,如果要不起,則將出牌請求轉發給他的下家,其下家再進行判斷。一個循環下來,如果其他人都要不起該牌,則最初的出