[.net 物件導向程式設計深入](31)實戰設計模式——使用IoC模式(控制反轉或依賴注入)實現鬆散耦合設計(1)
1,關於IOC模式
先看一些名詞含義:
IOC: Inversion of control 控制反轉,簡稱
DI: Dependency Injection 依賴注入,簡稱
DIP: 依賴倒置原則 一種軟體架構設計的原則(抽象概念),“設計模式使用場景及原則”一篇中介紹過設計模式的幾種原則之一。
IoC容器:依賴注入的框架,用來對映依賴,管理物件建立和生存週期(DI框架)。
(1)IOC和DI,是站在不同角度的描述,本身是同一個概念
先說說這兩個對於初次接觸到的同學難以理解的概念,首先這兩個東東是一回事,只是因為角度不同,有了兩個名字。
舉個不太恰當的例子,比如一個人,爸爸叫你兒子,爺爺叫你孫子,那這個兒子和孫子都是你,是同一個人,只是站的角度不同,這麼說容易理解了吧。
依賴注入(DI)是從應用程式的角度在描述,可以把依賴注入描述完整點:應用程式依賴容器建立並注入它所需要的外部資源;
而控制反轉(IOC)是從容器的角度在描述,描述完整點:容器控制應用程式,由容器反向的嚮應用程式注入應用程式所需要的外部資源。
(2)是一種大粒度的設計模式
和GOF的23種設計模式相比較。IOC模式(通常中文稱"依賴注入"或“依賴倒置”),由於出現時間比較晚,沒有被收錄。
其次和23種設計模式相比較,它的粒度更大一些,和MVC模式一樣,通常到架構層面了,而不是具體程式碼片段級別。理解到這裡就可以了。
但它仍然是設計模式,只是和23種設計模式比,23種模式是戰術層面,IOC模式是戰略層面的。
依賴注入對映到物件導向程式開發中就是:高層類應該依賴底層基礎設施來提供必要的服務。
編寫鬆耦合的程式碼說起來很簡單,但是實際上寫著寫著就變成了緊耦合。
一個比較難理解的正式定義如下:
依賴注入(Dependency Injection),是這樣一個過程:由於某客戶類只依賴於服務類的一個介面,而不依賴於具體服務類,所以客戶類只定義一個注入點。
在程式執行過程中,客戶類不直接例項化具體服務類例項,而是客戶類的執行上下文環境或專門元件負責例項化服務類,然後將其注入到客戶類中,保證客戶類的正常執行。
下面的說明比較容易理解:
理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰注入誰,注入了什麼”,那我們來深入分析一下:
●誰依賴於誰:當然是應用程式依賴於IoC容器;
●為什麼需要依賴:應用程式需要IoC容器來提供物件需要的外部資源;
●誰注入誰:很明顯是IoC容器注入應用程式某個物件,應用程式依賴的物件;
●注入了什麼:就是注入某個物件所需要的外部資源(包括物件、資源、常量資料)。
IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制物件這一個層面,很難讓人想到誰來維護物件關係),
所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入物件依賴IoC容器配置依賴物件”。
2,依賴注入的作用
依賴注入不是目的,它是一系列工具和手段。
最終的目的是幫助我們開發出鬆散耦合(loose coupled)、可維護、可測試的程式碼和程式。
這條原則的做法是大家熟知的面向介面,或者說是面向抽象程式設計。
常見的三層架構中,雖然表面上表現、業務、資料分層設計。但在資料庫層往往會產生一些與具體業務有關的類,而且如果不嚴格遵循程式碼規範,會導致產生表現層直接new資料層的情況。
如果要換個資料來源呢?假如不使用ADO.NET等試,改為Http呢?這將是領域邏輯層和表現層與之耦合的程式碼要進行大量更動。
這樣使得整個系統緊耦合,並且可測試性差。
在系統設計過程中,各個類從上層到下層類之間必然會產生耦合,如果完全沒有耦合,那麼這個類或程式集就可以從專案中移除了。
因此如何使之達到鬆散耦合,從而提高可測試性呢?依賴注入將能很好的解決上述問題。
3,IOC模式應用示例
下面以一個簡單購物過程為例來說明IOC模式如何實現鬆散耦合。
(1)傳統三層模式
程式碼如下:
public class DalSqlServer { public void Add() { Console.WriteLine("在資料庫中新增一條訂單!"); } }
public class Order { private readonly DalSqlServer dal = new DalSqlServer();//新增一個私有變數儲存資料庫操作的物件 public void Add() { dal.Add(); } }
class Program { static void Main(string[] args) { Order order = new Order(); order.Add(); Console.Read(); } }
執行結果:
上面的程式碼看著功能都實現了,然而突然老闆說,SqlServer要花錢買,我們使用免費的Access吧,好吧,那就改改嘍。改動後如下:
這時,我們只能再增加一個DalAcess類,來解決,程式碼如下:
public class DalAccess { public void Add() { Console.WriteLine("在Access資料庫中新增一條訂單!"); } }
public class Order { private readonly DalAccess dal = new DalAccess();//新增一個私有變數儲存Access資料庫操作的物件 public void Add() { dal.Add(); } }
class Program { static void Main(string[] args) { Order order = new Order(); order.Add(); Console.Read(); } }
執行結果:
正在這時,老闆來了,說最近生意好,訂單劇增,改成MySql資料庫吧,矇蔽了吧,示例程式碼中只有一個Add()的方法,可實際專案中,不知道有多少工作量了。
(2)使用IOC模式改進
只所以在需求變化時,我們的程式碼改動量如此之大,是因為耦合,耦合,耦合。
前面說了耦合不可能不存在,如果不存在,那這個程式碼就可以從專案中移除了,但是要讓讓程式碼可維護性強,就必須使用模式化的開發
當然依賴注入就能解決上述問題,依賴注入(DI),它提供一種機制,將需要依賴(低層模組)物件的引用傳遞給被依賴(高層模組)物件
我們示例中低層模組就是DalSqlServer,DalAccsess,DalMySql等,高層模組就是Order.
那麼如何在Order內部不依賴DalSqlServer,DalAccsess,DalMySql的情況下傳遞呢,
這就需要對DalSqlServer,DalAccsess,DalMySql進行抽象設計,我們設計一個IData資料介面物件,讓DalSqlServer,DalAccsess,DalMySql去具體實現它,
在傳遞過程中,我們只需要訂單處理Order和資料介面層IData耦合,這樣,即使資料庫再變化,但IData是穩定的。也不需要再改動Order中的程式碼了。是不是很不錯的解耦呢?
改進後如下:
程式碼如下:
public class DalOracle : IData { public void Add() { Console.WriteLine("在Oracle資料庫中新增一條訂單!"); } }
public class DalAccess : IData { public void Add() { Console.WriteLine("在Access資料庫中新增一條訂單!"); } }
public class DalMySql : IData { public void Add() { Console.WriteLine("在MySql資料庫中新增一條訂單!"); } }
public class DalSqlServer : IData { public void Add() { Console.WriteLine("在SqlServer資料庫中新增一條訂單!"); } }
定單處理類:
public class Order { private IData idata; //定義私有變數儲存抽象出來的資料介面 /// <summary> /// 通過建構函式注入 /// </summary> /// <param name="iData"></param> public Order(IData iData) { this.idata = iData; //傳遞依賴 } public void Add() { idata.Add(); } }
展示:
class Program { static void Main(string[] args) { //定義空訂單 Order order=null; //使用SqlServer order = new Order(new DalSqlServer()); order.Add(); //使用Oracle order = new Order(new DalOracle()); order.Add(); //使用Accesss order = new Order(new DalAccess()); order.Add(); //使用MySql order = new Order(new DalMySql()); order.Add(); Console.Read(); } }
這樣就可以隨意切換資料庫了,執行結果如下:
(3)更進一步改進,控制反轉
上面說到站在另一個角度講,我們把選擇資料庫的許可權交給第三方,是不是可以不用每次在建立訂單時都指定依賴物件(即具體資料類),也就是控制反轉。
針對上面的每次指定依賴物件的問題,處理的方式很多,最簡單的我們可以通過一個配置檔案來指定所使用的具體資料庫,在傳遞時通過反射的方式來對映資料類。
這樣就就靈活多了。
4,IOC注入的幾種方式
(1)建構函式注入
上面示例就是這種方式
(2)屬性注入
public class Order { public IData Idata{get;set;} public void Add() { this.Idata.Add(); } }
class Program { static void Main(string[] args) { //定義空訂單 Order order=null; //使用SqlServer order = new Order(); order.Idata = new DalSqlServer(); order.Add(); //使用Oracle order = new Order(); order.Idata = new DalOracle(); order.Add(); //使用Accesss order = new Order(); order.Idata = new DalAccess(); order.Add(); //使用MySql order = new Order(); order.Idata = new DalMySql(); order.Add(); Console.Read(); } }
(3)方法注入
public class Order { private IData idata; //私有變數儲存抽象介面 //通過Idata方法傳遞依賴 public void Idata(IData idata) { this.idata = idata; } public void Add() { this.idata.Add(); } }
class Program { static void Main(string[] args) { //定義空訂單 Order order=null; //使用SqlServer order = new Order(); order.Idata( new DalSqlServer()); order.Add(); //使用Oracle order = new Order(); order.Idata(new DalOracle()); order.Add(); //使用Accesss order = new Order(); order.Idata(new DalAccess()); order.Add(); //使用MySql order = new Order(); order.Idata(new DalMySql()); order.Add(); Console.Read(); } }
5,IOC容器(或DI框架)
對於大型專案來說,相互依賴的元件比較多。如果還用手動的方式,自己來建立和注入依賴的話,顯然效率很低,而且往往還會出現不可控的場面。正因如此,IoC容器誕生了。IoC容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能:
動態建立、注入依賴物件。
管理物件生命週期。
對映依賴關係。
比如比較知名的“基於DDD的現代ASP.NET開發框架--ABP”使用Castle Windsor框架處理依賴注入。它是最成熟的DI框架之一。還有很多其他的框架,如Unity,Ninject,StructureMap,Autofac等等。
下面是園友整理出來的一些常用的IOC容器及官網:
(1). Ninject: http://www.ninject.org/
(2). Castle Windsor: http://www.castleproject.org/container/index.html
(3). Autofac: http://code.google.com/p/autofac/
(4). StructureMap: http://docs.structuremap.net/
(5). Unity: http://unity.codeplex.com/
(6). MEF: http://msdn.microsoft.com/zh-cn/library/dd460648.aspx
(7). Spring.NET: http://www.springframework.net/
(8). LightInject: http://www.lightinject.net/ (推薦使用Chrome瀏覽器訪問)
6,總結
(1)依賴注入,是一種結構型的設計模式,即IOC模式。
(2)IOC意思為控制反轉和依賴注入是同一概念的不同角度的說法。
(3)依賴注入是讓我們的應用程式依賴於抽象出來的服務類的介面,而不是具體的服務類,從而在具體的服務類發生需求變化時,我們注入新的服務介面,做到鬆散耦合。
(4)依賴注入有三種簡單的方式,即建構函式注入,屬性注入,方法注入。
(5)在大型專案中為了解決手動建立注入的效率低下,誕生了IOC容器,常見的有:Unity、Ninject、StructureMap、Autofac、Spring.NET等。
7,原始碼
https://github.com/yubinfeng/BlogExamples.git
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>
<對本系列文章閱讀有困難的朋友,請先看 《.net 物件導向程式設計基礎》 和 《.net 物件導向程式設計進階》 >
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================