本篇幅主要介紹控制反轉的一些概念,和如何使用Unity實作Ioc。在介紹的時候,會盡量結合代碼來講解一些概念。
1.什麼是DI?
DI即控制反轉,是将對具體實作類的依賴轉變為對接口的依賴,這樣在程式設計中,就可以發揮類的多态性。我們先假設一台印鈔機,功能是列印鈔票,根據使用的模闆,可以印人民币(想到這裡,我做夢都樂了)。具體實作如下代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IocWithUnity
{
/// <summary>
/// 印鈔機
/// </summary>
public class CashMachine
{
public CashMachine() { }
public void Print()
{
CNYCashTemplate template = new CNYCashTemplate();
string templateContent = template.GetTemplate();
System.Console.WriteLine(templateContent);
}
}
/// <summary>
/// 人民币鈔票模闆
/// </summary>
public class CNYCashTemplate
{
public CNYCashTemplate() { }
public string GetTemplate()
{
return "這是人民币模闆";
}
}
}
複制
是不是很爽?可以印很多RMB了。哈哈哈哈!!!可是有一天,我們的機器要賣去美國,印美鈔,怎麼辦,于是,我們加了一個美鈔模闆。代碼如下:
/// <summary>
/// 美鈔鈔票模闆
/// </summary>
public class USDCashTemplate
{
public USDCashTemplate() { }
public string GetTemplate()
{
return "This is US Dollor.";
}
}
複制
這下尴尬了,我們還得再建一個USDCashMachine。同樣的代碼,再寫一遍。單是優秀的程式猿具有一個良好的特質--懶,是以我們打算改一下,成下面那樣:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IocWithUnity
{
/// <summary>
/// 印鈔機
/// </summary>
public class CashMachine
{
public CashMachine() { }
public void Print(ICashTemplate template)
{
string templateContent = template.GetTemplate();
System.Console.WriteLine(templateContent);
}
}
/// <summary>
/// 印鈔子產品
/// </summary>
public interface ICashTemplate
{
/// <summary>
/// 擷取鈔票模闆
/// </summary>
/// <returns></returns>
string GetTemplate();
}
/// <summary>
/// 人民币鈔票模闆
/// </summary>
public class CNYCashTemplate : ICashTemplate
{
public CNYCashTemplate() { }
public string GetTemplate()
{
return "這是人民币模闆";
}
}
/// <summary>
/// 美鈔鈔票模闆
/// </summary>
public class USDCashTemplate : ICashTemplate
{
public USDCashTemplate() { }
public string GetTemplate()
{
return "This is US Dollor.";
}
}
}
複制
我們在調用的時候就變成了
ICashTemplate template = new USDCashTemplate();
new CashMachine().Print(template);
複制
這個時候,我們想印美鈔,就放美鈔的模闆,想印人民币,就印人民币的模闆,厲害了吧?
沒錯,這就是面向接口的依賴反轉,我們的CashMachine從依賴CNYCashTemplate這個具體實作,變成了對ICashTemplate接口的依賴,在上面我們采用的是方法的注入,我們也可以用構造函數注入,用屬性注入,思路都是一樣的。這就是為什麼我們一直強調
面向接口程式設計,因為面向接口程式設計可以增強代碼結構的穩定性和可擴充性。
2.什麼是Ioc?
上面我們的印鈔機已經可以印各種鈔票了。那麼我們在實際開發當中,如果你進行了分層,想必應該是這樣的:
Cash.Business---業務層
Cash.Templates---鈔票模闆實作
Cash.IContract--接口層
那麼這三層的依賴關系應該是

作為一個有代碼潔癖的猿,肯定是不想有那麼多複雜的關系的。業界有一句著名的話怎麼說來着,沒有加一層解決不了的事情,如果有,那麼就加倆層。
變成這樣之後,是不是感覺簡潔很多了,沒有錯Infrustructure架構層做的事情就是這個,我們将建立具體對象的工作交給了架構,從此以後,CashBusiness的依賴關系就穩定了,我們也過上了衣食無憂,逍遙快樂的日子。
3.Unity的基本使用
上面Infrustructure的功能,我們使用的就是Unity。
public static class IocContainer
{
private static IUnityContainer _container = null;
static IocContainer()
{
_container = new UnityContainer();
}
/// <summary>
/// 注冊一個執行個體作為T的類型
/// </summary>
/// <typeparam name="T">需要注冊的類型</typeparam>
/// <param name="instance">需要注冊的執行個體</param>
public static void Register<T>(T instance)
{
_container.RegisterInstance<T>(instance);
}
/// <summary>
/// 注冊一個名為name的T類型的執行個體
/// </summary>
/// <typeparam name="T">需要注冊的類型</typeparam>
/// <param name="name">關鍵字名稱</param>
/// <param name="instance">執行個體</param>
public static void Register<T>(string name, T instance)
{
_container.RegisterInstance(name, instance);
}
/// <summary>
/// 将類型TFrom注冊為類型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
public static void Register<TFrom, TTo>() where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>();
}
/// <summary>
/// 将類型TFrom注冊為類型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
/// <typeparam name="lifetime"></typeparam>
public static void Register<TFrom, TTo>(LifetimeManager lifetime) where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>(lifetime);
}
/// <summary>
/// 将類型TFrom注冊名為name類型TTo
/// </summary>
/// <typeparam name="TFrom"></typeparam>
/// <typeparam name="TTo"></typeparam>
public static void Register<TFrom, TTo>(string name) where TTo : TFrom
{
_container.RegisterType<TFrom, TTo>(name);
}
/// <summary>
/// 通過關鍵字name來擷取一個執行個體對象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
public static T Resolve<T>(string name)
{
return _container.Resolve<T>(name);
}
/// <summary>
/// 擷取一個為T類型的對象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Resolve<T>()
{
return _container.Resolve<T>();
}
/// <summary>
/// 擷取所有注冊類型為T的對象執行個體
/// </summary>
/// <typeparam name="T">需要擷取的類型的對象</typeparam>
/// <returns></returns>
public static IEnumerable<T> ResolveAll<T>()
{
return _container.ResolveAll<T>();
}
}
複制
上面代碼中Register就是将對象或實作類,注冊到Ioc容器中,在需要使用的地方再調用Resolve擷取對象即可,這樣,無論我們在哪裡需要,都可以用Ioc容器來擷取對象,而不再需要使用new來建立對象了。
4.使用配置檔案配置注入
但是,我們顯然不滿足于這樣,我們還想把實作,徹徹底底的從代碼中移除,這裡我們就可以借助配置檔案來實作了。首先,我們在IocContainer裡添加一個靜态構造函數,讓程式在初次使用IocContainer時加載配置,
static IocContainer()
{
_container = new UnityContainer();
object unitySection = ConfigurationManager.GetSection("unity");
if (unitySection == null) return;
UnityConfigurationSection section = (UnityConfigurationSection)unitySection;
section.Configure(_container, "Default");
}
/// <summary>
/// 從檔案中加載Unity注入的對象和映射關系等
/// </summary>
/// <param name="configFile">Unity容器配置檔案的路徑</param>
public static void LoadUnityConfig(string configFile)
{
string filePath = configFile;
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = filePath };
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection unitySection = (UnityConfigurationSection)configuration.GetSection("unity");
foreach (var item in unitySection.Containers)
{
_container.LoadConfiguration(unitySection, item.Name);
}
}
複制
web.config(WEB項目是在web.config)裡配置我們配置檔案的路徑,在configuration節點中添加如下配置
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity configSource="unity.config"/>
複制
接下來我們來配置我們的unity.config檔案(這裡unity.config是放在運作目錄下的,WEB網站下應該是與bin目錄同級)
<?xml version="1.0" encoding="utf-8" ?>
<unity xmlns= "http://schemas.microsoft.com/practices/2010/unity ">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
<!--注入對象-->
<typeAliases>
<!--表示單例-->
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,Microsoft.Practices.Unity" />
<!--表示每次使用都進行建立-->
<typeAlias alias="transient" type="Microsoft.Practices.Unity.TransientLifetimeManager,Microsoft.Practices.Unity" />
</typeAliases>
<container name= "Default">
<!--配置sql-->
<!--<register type= "接口類,接口dll" mapTo= "實作類,實作dll" name="執行個體名">-->
<register type= "MJD.Framework.Sql.Interfaces.IConnectionFactory,MJD.Framework.Sql" mapTo= "MJD.Framework.Sql.Oracle.ConnectionFactory,MJD.Framework.Sql.Oracle">
<lifetime type="singleton" />
</register>
</container>
</unity>
複制
這樣,我們就可以将實作徹底的解耦出來了。怎麼樣,是不是很酷?以後再也不需要再去更改代碼了,直接配置就可以了。
5.三種生命周期
在上面的配置中,眼尖的你可能會發現,在register下還配置了一個lifetime,type填寫的是一個别名。這裡就是所謂的生命周期,在Unity中有三種生命周期
ContainerControlledLifetimeManager,即單例,生命周期與容器的生命周期一樣,一般如果我們使用靜态的容器,那麼這個就等同于我們的單例模式;
複制
TransientLifetimeManager,臨時的,即每次建立容器都會new一個對象給我們使用;
HierarchicalLifetimeManager,這個用得比較少,假如容器有分層,有子容器,那麼父容器與子容器中的對象都是單例的,但是子類與父類裡的對象不是同一個;
複制