简述控制反转ioc_讲一下你理解的 DI 、IoC、DIP ?
作者 |?木小楠
鏈接 |cnblogs.com/liuhaorain/p/3747470.html
摘要
面向?qū)ο笤O計(OOD)有助于我們開發(fā)出高性能、易擴展以及易復用的程序。其中,OOD有一個重要的思想那就是依賴倒置原則(DIP),并由此引申出IoC、DI以及Ioc容器等概念。本文我們將一起學習這些概念,并理清他們之間微妙的關系。在學習之前,大家可以把自己的理解發(fā)表在留言區(qū),共同探討。
目錄
- 前言
- 依賴倒置原則(DIP)
- 控制反轉(zhuǎn)(IoC)
- 依賴注入(DI)
- IoC容器
- 總結(jié)
前言
對于大部分小菜來說,當聽到大牛們高談DIP、IoC、DI以及IoC容器等名詞時,有沒有瞬間石化的感覺?其實,這些“高大上”的名詞,理解起來也并不是那么的難,關鍵在于入門。只要我們?nèi)腴T了,然后循序漸進,假以時日,自然水到渠成。
好吧,我們先初略了解一下這些概念。
依賴倒置原則(DIP):一種軟件架構(gòu)設計的原則(抽象概念)。
控制反轉(zhuǎn)(IoC):一種反轉(zhuǎn)流、依賴和接口的方式(DIP的具體實現(xiàn)方式)。
依賴注入(DI):IoC的一種實現(xiàn)方式,用來反轉(zhuǎn)依賴(IoC的具體實現(xiàn)方式)。
IoC容器:依賴注入的框架,用來映射依賴,管理對象創(chuàng)建和生存周期(DI框架)。
哦!也許你正為這些陌生的概念而傷透腦筋。不過沒關系,接下來我將為你一一道破這其中的玄機。
依賴倒置原則(DIP)
在講概念之前,我們先看生活中的一個例子。
相信大部分取過錢的朋友都深有感觸,只要有一張卡,隨便到哪一家銀行的ATM都能取錢。在這個場景中,ATM相當于高層模塊,而銀行卡相當于低層模塊。ATM定義了一個插口(接口),供所有的銀行卡插入使用。也就是說,ATM不依賴于具體的哪種銀行卡。
它只需定義好銀行卡的規(guī)格參數(shù)(接口),所有實現(xiàn)了這種規(guī)格參數(shù)的銀行卡都能在ATM上使用。現(xiàn)實生活如此,軟件開發(fā)更是如此。依賴倒置原則,它轉(zhuǎn)換了依賴,高層模塊不依賴于低層模塊的實現(xiàn),而低層模塊依賴于高層模塊定義的接口。通俗的講,就是高層模塊定義接口,低層模塊負責實現(xiàn)。
Bob Martins對DIP的定義:?高層模塊不應依賴于低層模塊,兩者應該依賴于抽象。?抽象不不應該依賴于實現(xiàn),實現(xiàn)應該依賴于抽象。如果生活中的實例不足以說明依賴倒置原則的重要性,那下面我們將通過軟件開發(fā)的場景來理解為什么要使用依賴倒置原則。
場景一? 依賴無倒置(低層模塊定義接口,高層模塊負責實現(xiàn))
從上圖中,我們發(fā)現(xiàn)高層模塊的類依賴于低層模塊的接口。因此,低層模塊需要考慮到所有的接口。如果有新的低層模塊類出現(xiàn)時,高層模塊需要修改代碼,來實現(xiàn)新的低層模塊的接口。這樣,就破壞了開放封閉原則。
場景二 依賴倒置(高層模塊定義接口,低層模塊負責實現(xiàn))
在這個圖中,我們發(fā)現(xiàn)高層模塊定義了接口,將不再直接依賴于低層模塊,低層模塊負責實現(xiàn)高層模塊定義的接口。這樣,當有新的低層模塊實現(xiàn)時,不需要修改高層模塊的代碼。
由此,我們可以總結(jié)出使用DIP的優(yōu)點:
系統(tǒng)更柔韌:可以修改一部分代碼而不影響其他模塊。
系統(tǒng)更健壯:可以修改一部分代碼而不會讓系統(tǒng)崩潰。
系統(tǒng)更高效:組件松耦合,且可復用,提高開發(fā)效率。
控制反轉(zhuǎn)(IoC)
DIP是一種軟件設計原則,它僅僅告訴你兩個模塊之間應該如何依賴,但是它并沒有告訴如何做。IoC則是一種軟件設計模式,它告訴你應該如何做,來解除相互依賴模塊的耦合。控制反轉(zhuǎn)(IoC),它為相互依賴的組件提供抽象,將依賴(低層模塊)對象的獲得交給第三方(系統(tǒng))來控制,即依賴對象不在被依賴模塊的類中直接通過new來獲取。
在圖1的例子我們可以看到,ATM它自身并沒有插入具體的銀行卡(工行卡、農(nóng)行卡等等),而是將插卡工作交給人來控制,即我們來決定將插入什么樣的銀行卡來取錢。同樣我們也通過軟件開發(fā)過程中場景來加深理解。軟件設計原則:原則為我們提供指南,它告訴我們什么是對的,什么是錯的。它不會告訴我們?nèi)绾谓鉀Q問題。它僅僅給出一些準則,以便我們可以設計好的軟件,避免不良的設計。一些常見的原則,比如DRY、OCP、DIP等。
軟件設計模式:模式是在軟件開發(fā)過程中總結(jié)得出的一些可重用的解決方案,它能解決一些實際的問題。一些常見的模式,比如工廠模式、單例模式等等。
做過電商網(wǎng)站的朋友都會面臨這樣一個問題:訂單入庫。假設系統(tǒng)設計初期,用的是SQL Server數(shù)據(jù)庫。通常我們會定義一個SqlServerDal類,用于數(shù)據(jù)庫的讀寫。
public class SqlServerDal{ public void Add() { Console.WriteLine("在數(shù)據(jù)庫中添加一條訂單!"); }}然后我們定義一個Order類,負責訂單的邏輯處理。由于訂單要入庫,需要依賴于數(shù)據(jù)庫的操作。因此在Order類中,我們需要定義SqlServerDal類的變量并初始化。
public class Order{ private readonly SqlServerDal dal = new SqlServerDal();//添加一個私有變量保存數(shù)據(jù)庫操作的對象 public void Add() { dal.Add(); }}最后,我們寫一個控制臺程序來檢驗成果。
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace DIPTest{ class Program { static void Main(string[] args) { Order order = new Order(); order.Add(); Console.Read(); } }}?輸出結(jié)果:
OK,結(jié)果看起來挺不錯的!正當你沾沾自喜的時候,這時BOSS過來了。“小劉啊,剛客戶那邊打電話過來說數(shù)據(jù)庫要改成Access”,“對你來說,應當小CASE啦!”BOSS又補充道。帶著自豪而又糾結(jié)的情緒,我們思考著修改代碼的思路。
?由于換成了Access數(shù)據(jù)庫,SqlServerDal類肯定用不了了。因此,我們需要新定義一個AccessDal類,負責Access數(shù)據(jù)庫的操作。
public class AccessDal{ public void Add() { Console.WriteLine("在ACCESS數(shù)據(jù)庫中添加一條記錄!"); }}?然后,再看Order類中的代碼。由于,Order類中直接引用了SqlServerDal類的對象。所以還需要修改引用,換成AccessDal對象。
public class Order{ private readonly AccessDal dal = new AccessDal();//添加一個私有變量保存數(shù)據(jù)庫操作的對象 public void Add() { dal.Add(); }}輸出結(jié)果:
費了九牛二虎之力,程序終于跑起來了!試想一下,如果下次客戶要換成MySql數(shù)據(jù)庫,那我們是不是還得重新修改代碼?
顯然,這不是一個良好的設計,組件之間高度耦合,可擴展性較差,它違背了DIP原則。高層模塊Order類不應該依賴于低層模塊SqlServerDal,AccessDal,兩者應該依賴于抽象。那么我們是否可以通過IoC來優(yōu)化代碼呢?答案是肯定的。IoC有2種常見的實現(xiàn)方式:依賴注入和服務定位。其中,依賴注入使用最為廣泛。下面我們將深入理解依賴注入(DI),并學會使用。
依賴注入(DI)
控制反轉(zhuǎn)(IoC)一種重要的方式,就是將依賴對象的創(chuàng)建和綁定轉(zhuǎn)移到被依賴對象類的外部來實現(xiàn)。在上述的實例中,Order類所依賴的對象SqlServerDal的創(chuàng)建和綁定是在Order類內(nèi)部進行的。事實證明,這種方法并不可取。既然,不能在Order類內(nèi)部直接綁定依賴關系,那么如何將SqlServerDal對象的引用傳遞給Order類使用呢?
依賴注入(DI),它提供一種機制,將需要依賴(低層模塊)對象的引用傳遞給被依賴(高層模塊)對象。通過DI,我們可以在Order類的外部將SqlServerDal對象的引用傳遞給Order類對象。那么具體是如何實現(xiàn)呢?
方法一 構(gòu)造函數(shù)注入
構(gòu)造函數(shù)函數(shù)注入,毫無疑問通過構(gòu)造函數(shù)傳遞依賴。因此,構(gòu)造函數(shù)的參數(shù)必然用來接收一個依賴對象。那么參數(shù)的類型是什么呢?具體依賴對象的類型?還是一個抽象類型?根據(jù)DIP原則,我們知道高層模塊不應該依賴于低層模塊,兩者應該依賴于抽象。那么構(gòu)造函數(shù)的參數(shù)應該是一個抽象類型。我們再回到上面那個問題,如何將SqlServerDal對象的引用傳遞給Order類使用呢?
首選,我們需要定義SqlServerDal的抽象類型IDataAccess,并在IDataAccess接口中聲明一個Add方法。
public interface IDataAccess{ void Add();}?然后在SqlServerDal類中,實現(xiàn)IDataAccess接口。
public class SqlServerDal:IDataAccess{ public void Add() { Console.WriteLine("在數(shù)據(jù)庫中添加一條訂單!"); }}接下來,我們還需要修改Order類。 public class Order { private IDataAccess _ida;//定義一個私有變量保存抽象 //構(gòu)造函數(shù)注入 public Order(IDataAccess ida) { _ida = ida;//傳遞依賴 } public void Add() { _ida.Add(); }}?OK,我們再來編寫一個控制臺程序。
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace DIPTest{ class Program { static void Main(string[] args) { SqlServerDal dal = new SqlServerDal();//在外部創(chuàng)建依賴對象 Order order = new Order(dal);//通過構(gòu)造函數(shù)注入依賴 order.Add(); Console.Read(); } }}?輸出結(jié)果:
從上面我們可以看出,我們將依賴對象SqlServerDal對象的創(chuàng)建和綁定轉(zhuǎn)移到Order類外部來實現(xiàn),這樣就解除了SqlServerDal和Order類的耦合關系。當我們數(shù)據(jù)庫換成Access數(shù)據(jù)庫時,只需定義一個AccessDal類,然后外部重新綁定依賴,不需要修改Order類內(nèi)部代碼,則可實現(xiàn)Access數(shù)據(jù)庫的操作。
定義AccessDal類:
public class AccessDal:IDataAccess{ public void Add() { Console.WriteLine("在ACCESS數(shù)據(jù)庫中添加一條記錄!"); }}然后在控制臺程序中重新綁定依賴關系:
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部創(chuàng)建依賴對象 Order order = new Order(dal);//通過構(gòu)造函數(shù)注入依賴 order.Add(); Console.Read(); } }}輸出結(jié)果:
顯然,我們不需要修改Order類的代碼,就完成了Access數(shù)據(jù)庫的移植,這無疑體現(xiàn)了IoC的精妙。
?方法二 屬性注入
顧名思義,屬性注入是通過屬性來傳遞依賴。因此,我們首先需要在依賴類Order中定義一個屬性:
public class Order{ private IDataAccess _ida;//定義一個私有變量保存抽象 //屬性,接受依賴 public IDataAccess Ida { set { _ida = value; } get { return _ida; } } public void Add() { _ida.Add(); }}?然后在控制臺程序中,給屬性賦值,從而傳遞依賴:
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部創(chuàng)建依賴對象 Order order = new Order(); order.Ida = dal;//給屬性賦值 order.Add(); Console.Read(); } }}我們可以得到上述同樣的結(jié)果。
方法三 接口注入
相比構(gòu)造函數(shù)注入和屬性注入,接口注入顯得有些復雜,使用也不常見。具體思路是先定義一個接口,包含一個設置依賴的方法。然后依賴類,繼承并實現(xiàn)這個接口。
首先定義一個接口:?
public interface IDependent{ void SetDependence(IDataAccess ida);//設置依賴項}依賴類實現(xiàn)這個接口:
public class Order : IDependent { private IDataAccess _ida;//定義一個私有變量保存抽象 //實現(xiàn)接口 public void SetDependence(IDataAccess ida) { _ida = ida; } public void Add() { _ida.Add(); } }控制臺程序通過SetDependence方法傳遞依賴:
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace DIPTest{ class Program { static void Main(string[] args) { AccessDal dal = new AccessDal();//在外部創(chuàng)建依賴對象 Order order = new Order(); order.SetDependence(dal);//傳遞依賴 order.Add(); Console.Read(); } }}我們同樣能得到上述的輸出結(jié)果。
IoC容器
前面所有的例子中,我們都是通過手動的方式來創(chuàng)建依賴對象,并將引用傳遞給被依賴模塊。比如:
SqlServerDal dal = new SqlServerDal();Order order = new Order(dal);?對于大型項目來說,相互依賴的組件比較多。如果還用手動的方式,自己來創(chuàng)建和注入依賴的話,顯然效率很低,而且往往還會出現(xiàn)不可控的場面。正因如此,IoC容器誕生了。IoC容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能:
動態(tài)創(chuàng)建、注入依賴對象。
管理對象生命周期。
映射依賴關系。
目前,比較流行的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瀏覽器訪問)
以Ninject為例,我們同樣來實現(xiàn)[方法一 構(gòu)造函數(shù)注入]的功能
首先在項目添加Ninject程序集,同時使用using指令引入。?
using Ninject;然后,Ioc容器注冊綁定依賴:
StandardKernel kernel = new StandardKernel(); kernel.Bind().To();//注冊依賴接下來,我們獲取需要的Order對象(注入了依賴對象):
Order order = kernel.Get();?下面,我們寫一個完整的控制臺程序
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Ninject; namespace DIPTest{ class Program { static void Main(string[] args) { StandardKernel kernel = new StandardKernel();//創(chuàng)建Ioc容器 kernel.Bind().To();//注冊依賴 Order order = kernel.Get();//獲取目標對象 order.Add(); Console.Read(); } }}?輸出結(jié)果:
使用IoC容器,我們同樣實現(xiàn)了該功能。
總結(jié)
在本文中,我試圖以最通俗的方式講解,希望能幫助大家理解這些概念。下面我們一起來總結(jié)一下:DIP是軟件設計的一種思想,IoC則是基于DIP衍生出的一種軟件設計模式。DI是IoC的具體實現(xiàn)方式之一,使用最為廣泛。IoC容器是DI構(gòu)造函注入的框架,它管理著依賴項的生命周期以及映射關系。
如果喜歡本篇文章,歡迎轉(zhuǎn)發(fā)、點贊。關注訂閱號「Web項目聚集地」,回復「技術博文」即可獲取更多圖文教程、技術博文。
推薦閱讀
1.?因 轉(zhuǎn)賬失敗 引發(fā)的思考...
2.?如何優(yōu)雅地編碼?
3.?Java 程序員“吃完飯直接走”
4.?一鍵登陸了解一下?
5.?9 張 Java 技術流程圖
6.?MySQL 請不要使用“utf-8”
7.?還不懂 Java 中的多線程 ?
8.?如何編寫輕量級 CSS 框架
總結(jié)
以上是生活随笔為你收集整理的简述控制反转ioc_讲一下你理解的 DI 、IoC、DIP ?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript 随意整理2
- 下一篇: 初学者最常问的几个问题