天天看點

控制反轉-Ioc之Unity

本篇幅主要介紹控制反轉的一些概念,和如何使用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--接口層

那麼這三層的依賴關系應該是

控制反轉-Ioc之Unity

作為一個有代碼潔癖的猿,肯定是不想有那麼多複雜的關系的。業界有一句著名的話怎麼說來着,沒有加一層解決不了的事情,如果有,那麼就加倆層。

控制反轉-Ioc之Unity

變成這樣之後,是不是感覺簡潔很多了,沒有錯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,這個用得比較少,假如容器有分層,有子容器,那麼父容器與子容器中的對象都是單例的,但是子類與父類裡的對象不是同一個;           

複制