[转]IOC简介
一、IoC 簡介
IoC的全名是『Inversion of Control』,字面上的意思是『控制反轉(zhuǎn)』,要了解這個名詞的真正含意,得從『控制』這個詞切入。一般來說,當(dāng)設(shè)計師撰寫一個Console程序時,控制權(quán)是在該程序上,它決定著何時該印出訊息、何時又該接受使用者輸入、何時該進行數(shù)據(jù)處理,如程序1。
程序1
using System;using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
??? class Program
??? {
??????? static void Main(string[] args)
??????? {
??????????? Console.Write("Please Input Some Words:");
??????????? string inputData = Console.ReadLine();
??????????? Console.WriteLine(inputData);
??????????? Console.Read();
??????? }
??? }
}
?
從整個流程上看來,OS將控制權(quán)交給了此程序,接下來就看此程序何時將控制權(quán)交回,這是Console模式的標準處理流程。程序1演譯了『控制』這個字的意思,那么『反轉(zhuǎn)』這個詞的含義呢?這可以用一個Windows Application來演示,如程序2。
程序2
using System;using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
?
namespace WindowsApplication10
{
??? public partial class Form1 : Form
??? {
??????? public Form1()
??????? {
??????????? InitializeComponent();
??????? }
?
??????? private void button1_Click(object sender, EventArgs e)
??????? {
??????????? MessageBox.Show(textBox1.Text);
??????? }
??? }
}
與程序1不同,當(dāng)程序2被執(zhí)行后,控制權(quán)其實并不在此程序中,而是在底層的Windows Forms Framework上,當(dāng)此程序執(zhí)行后,控制權(quán)會在Application.Run函數(shù)調(diào)用后,由主程序轉(zhuǎn)移到Windows Forms Framework上,進入等待訊息的狀態(tài),當(dāng)用戶按下了Form上的按鈕后,底層的Windows Forms Framework會收到一個訊息,接著會依照訊息來 調(diào)用button1_Click方法,此時控制權(quán)就由Windows Forms Framework轉(zhuǎn)移到了主程序。程序2充份演譯了『控制反轉(zhuǎn)』的意含,也就是將原本位于主程序中的控制權(quán),反轉(zhuǎn)到了Windows Forms Framework上。
二、Dependency Injection
IoC的中心思想在于控制權(quán)的反轉(zhuǎn),這個概念于現(xiàn)今的Framework中相當(dāng)常見,.NET Framework中就有許多這樣的例子,問題是!既然這個概念已經(jīng) 實現(xiàn)于許多Framework中,那為何近年來IoC會于社群引起這么多的討論?著名的IoC實現(xiàn)對象如Avalon、Spring又達到了什么目的呢?就筆者的認知,IoC是一個廣泛的概念,主要中心思想就在于控制權(quán)的反轉(zhuǎn),Windows Forms Framework與Spring在IoC的大概念下,都可以算是IoC的實現(xiàn)對象,兩者不同之處在于究竟反轉(zhuǎn)了那一部份的控制權(quán),Windows Forms Framework將主程序的控制權(quán)反轉(zhuǎn)到了自身上,Spring則是將對象的建立、釋放、配置等控制權(quán)反轉(zhuǎn)到自身,雖然兩者都符合IoC的大概念,但設(shè)計初衷及欲達成的目的完全不同,因此用IoC來統(tǒng)稱兩者,就顯得有些籠統(tǒng)及模糊。設(shè)計大師Martin Fowler針對Spring這類型IoC實現(xiàn)對象提出了一個新的名詞『Dependency Injection』,字面上的意思是『依賴注入』。對筆者而言,這個名詞比起IoC更能描述現(xiàn)今許多宣稱支持IoC的Framework內(nèi)部的行為,在Martin Fowler的解釋中, Dependency Injection分成三種,一是Interface Injection(接口注射)、Constructor Injection(構(gòu)造函數(shù)注射)、Setter Injection(設(shè)值注射)。
2-1、Why we need Dependency Injection?
OK,花了許多篇幅在解釋IoC與Dependency Injection兩個概念,希望讀者們已經(jīng)明白這兩個名詞的涵意,在切入Dependency Injection這個主題前,我們要先談?wù)劄楹我褂肈ependency Injection,及這樣做帶來了什么好處,先從程序3的例子開始。
程序3
using System;using System.Collections.Generic;
using System.Text;
?
namespace DISimple
{
??? class Program
??? {
??????? static void Main(string[] args)
??????? {
??????????? InputAccept accept = new InputAccept(new PromptDataProcessor());
??????????? accept.Execute();
??????????? Console.ReadLine();
??????? }
??? }
?
??? public class InputAccept
??? {
??????? private IDataProcessor _dataProcessor;
?
??????? public void Execute()
??????? {
??????????? Console.Write("Please Input some words:");
??????????? string input = Console.ReadLine();
??????????? input = _dataProcessor.ProcessData(input);
??????????? Console.WriteLine(input);
??????? }
?
??????? public InputAccept(IDataProcessor dataProcessor)
??????? {
??????????? _dataProcessor = dataProcessor;
??????? }
??? }
?
??? public interface IDataProcessor
??? {
??????? string ProcessData(string input);
??? }
?
??? public class DummyDataProcessor : IDataProcessor
??? {
?
??????? #region IDataProcessor Members
?
??????? public string ProcessData(string input)
??????? {
??????????? return input;
??????? }
?
??????? #endregion
??? }
?
??? public class PromptDataProcessor : IDataProcessor
??? {
??????? #region IDataProcessor Members
?
??????? public string ProcessData(string input)
??????? {
??????????? return "your input is: " + input;
??????? }
?
??????? #endregion
??? }
}
這是一個簡單且無用的例子,但卻可以告訴我們?yōu)楹我褂肈ependency Injection,在這個例子中,必須在建立InputAccept對象時傳入一 個實現(xiàn)IDataProcessor接口的對象,這是Interface Base Programming概念的設(shè)計模式,這樣做的目的是為了降低InputAccept與實現(xiàn)對象間的耦合關(guān)系,重用InputAccept的執(zhí)行流程,以此來增加程序的延展性。那這個設(shè)計有何不當(dāng)之處呢?沒有!問題不在InputAccept、IDataProcessor的設(shè)計,而在于使用的方式。
InputAccept accept = new InputAccept(new PromptDataProcessor());使用InputAccept時,必須在建立對象時傳入一個實現(xiàn)IDataProcess接口的對象,此處直接建立一個PromptDataProcessor對象傳入,這使得主程序與PromptDataProcessor對象產(chǎn)生了關(guān)聯(lián)性,間接的摧毀使用IDataProcessor時所帶來的低耦合性,那要如何解決這個問題呢?讀過Design Patterns的讀者會提出以Builder、Factory等樣式解決這個問題,如下所示。
//FactoryInputAccept accept = new InputAccept(DataProcessorFactory.Create());
//Builder
InputAccept accept = new InputAccept(DataProcessorBulder.Build());
兩者的實際流程大致相同,DataProcessorFactory.Create方法會依據(jù)組態(tài)檔的設(shè)定來建立指定的IDataProcessor實現(xiàn)對象,回傳后指定給InputAccept,DataProcessBuilder.Build方法所做的事也大致相同。這樣的設(shè)計是將原本位于主程序中IDataProcessor對象的建立動作,轉(zhuǎn)移到DataProcessorFactory、DataProcessorBuilder上,這也算是一種IoC觀念的實現(xiàn),只是這種轉(zhuǎn)移同時也將主程序與IDataProcessor對象間的關(guān)聯(lián),平移成主程序與DataProcessorFactory間的關(guān)聯(lián),當(dāng)需要建立的對象一多時,問題又將回到原點,程序中一定會充斥著AFactory、BFactory等Factory對象。徹底將關(guān)聯(lián)性降到最低的方法很簡單,就是設(shè)計Factory的Factory、或是Builder的Builder,如下所示。
//declarepublic class DataProcessorFactory : IFactory
..........
//Builder
public class DataProcessorBuilder : IBuilder
...........
....................
//initialize
//Factory?
GenericFactory.RegisterTypeFactory(typeof(IDataProcessor),typeof(DataProcessorFactory));
//Builder
GenericFactory.RegisterTypeBuilder(typeof(IDataProcessor),typeof(DataProcessorBuilder));
................
//Factory
InputAccept accept = new InputAccept(GenericFactory.Create(typeof(IDataProcessor));
//Builder
InputAccept accept = new InputAccept(GenericBuilder.Build(typeof(IDataProcessor));
這個例子中,利用了一個GenericFactory對象來建立InputAccept所需的IDataProcessor對象,當(dāng)GenericFactory.Create方法被 調(diào)用時,它會查詢所擁有的Factory對象對應(yīng)表,這個對應(yīng)表是以type of base class/type of factory成對的格式存放,程序必須在一啟動時準備好這個對應(yīng)表,這可以透過組態(tài)檔或是程序代碼來完成,GenericFactory.Create方法在找到所傳入的type of base class所對應(yīng)的type of factory后,就建立該Factory的實體,然后調(diào)用該Factory對象的Create方法來建立IDataProcessor對象實體后回傳。另外,為了統(tǒng)一Factory的 調(diào)用方式,GenericFactory要求所有注冊的Factory對象必須實現(xiàn)IFactory接口,此接口只有一個需要實現(xiàn)的方法:Create。方便讀者易于理解這個設(shè)計概念,圖1以流程圖呈現(xiàn)這個設(shè)計的。
圖1
那這樣的設(shè)計有何優(yōu)勢?很明顯的,這個設(shè)計已經(jīng)將主程序與DataProcessorFactory關(guān)聯(lián)切除,轉(zhuǎn)移成主程序與GenericFactory的關(guān)聯(lián),由于只使用一個Factory:GenericFactory,所以不存在于AFactory、BFactory這類問題。這樣的設(shè)計概念確實降低了對象間的關(guān)聯(lián)性,但仍然不夠完善,因為有時對象的構(gòu)造函數(shù)會需要一個以上的參數(shù),但GenericFactory卻未提供途徑來傳入這些參數(shù)(想象當(dāng)InputAccept也是經(jīng)由GenericFactory建立時),當(dāng)然!我們可以運用object[]、params等途徑來傳入這些參數(shù),只是這么做的后果是,主程序會與實體對象的構(gòu)造函數(shù)產(chǎn)生關(guān)聯(lián),也就是間接的與實體對象產(chǎn)生關(guān)聯(lián)。要切斷這層關(guān)聯(lián),我們可以讓GenericFactory自動完成InputAccept與IDataProcessor實體對象間的關(guān)聯(lián),也就是說在GenericFactory中,依據(jù)InputAccept的構(gòu)造 函數(shù)聲明,取得參數(shù)類型,然后使用該參數(shù)類型(此例就是IDataProcessor)來調(diào)用GenericFactory.Create方法建立實體的對象,再將這個對象傳給InputAccept的構(gòu)造函數(shù),這樣主程序就不會與InputAccept的構(gòu)造函數(shù)產(chǎn)生關(guān)聯(lián),這就是Constructor Injection(構(gòu)造函數(shù)注入)的概念。以上的討論,我們可以理出幾個重點,一、Dependency Injection是用來降低主程序與對象間的關(guān)聯(lián),二、Dependency Injection同時也能降低對象間的互聯(lián)性,三、Dependency Injection可以簡化對象的建立動作,進而讓對象更容易使用,試想!只要調(diào)用GenericFactory.Create(typeof(InputAccept))跟原先的設(shè)計,那個更容易使用?不過要擁有這些優(yōu)點,我們得先擁有著一個完善的架構(gòu),這就是ObjectBuilder、Spring、Avalon等Framework出現(xiàn)的原因。
PS:這一小節(jié)進度超前許多,接下來將回歸Dependency Injection的三種模式,請注意!接下來幾小節(jié)的討論是依據(jù)三種模式的精神,所以例子以簡單易懂為主,不考慮本文所提及的完整架構(gòu)。
2-2、Interface Injection
Interface Injection指的是將原本建構(gòu)于對象間的依賴關(guān)系,轉(zhuǎn)移到一個接口上,程序4是一個簡單的例子。
程序4
using System;using System.Collections.Generic;
using System.Text;
?
namespace ConsoleApplication2
{
??? class Program
??? {
??????? static void Main(string[] args)
??????? {
??????????? InputAccept accept = new InputAccept();
??????????? accept.Inject(new DummyDataProcessor());
??????????? accept.Execute();
??????????? Console.Read();
??????? }
??? }
?
??? public class InputAccept
??? {
??????? private IDataProcessor _dataProcessor;
?
??????? public void Inject(IDataProcessor dataProcessor)
??????? {
??????????? _dataProcessor = dataProcessor;
??????? }
?
??????? public void Execute()
??????? {
??????????? Console.Write("Please Input some words:");
??????????? string input = Console.ReadLine();
??????????? input = _dataProcessor.ProcessData(input);
??????????? Console.WriteLine(input);
??????? }
??? }
?
??? public interface IDataProcessor
??? {
??????? string ProcessData(string input);
??? }
?
??? public class DummyDataProcessor : IDataProcessor
??? {
?
??????? #region IDataProcessor Members
?
??????? public string ProcessData(string input)
??????? {
??????????? return input;
??????? }
?
??????? #endregion
??? }
?
??? public class PromptDataProcessor : IDataProcessor
??? {
??????? #region IDataProcessor Members
?
??????? public string ProcessData(string input)
??????? {
??????????? return "your input is: " + input;
??????? }
?
??????? #endregion
??? }
}
InputAccept對象將一部份的動作轉(zhuǎn)移到另一個對象上,雖說如此,但InputAccept與該對象并未建立依賴關(guān)系,而是將依賴關(guān)系建立在一個接口:IDataProcessor上,經(jīng)由一個方法傳入實體對象,我們將這種應(yīng)用稱為Interface Injection。當(dāng)然,如你所見,程序4的手法在實務(wù)應(yīng)用上并未帶來太多的好處,原因是執(zhí)行Interface Injection動作的仍然是主程序,這意味著與主程序與該對象間的依賴關(guān)系仍然存在,要將Interface Injection的概念發(fā)揮到極致的方式有兩個,一是使用組態(tài)文件,讓主程序由組態(tài)文件中讀入DummaryDataProcessor或是PromptDataProcessor,這樣一來,主程序便可以在不重新編譯的情況下,改變InputAccept對象的行為。二是使用Container(容器),Avalon是一個標準的范例。
程序5
public class InputAccept implements Serviceable {?private IDataProcessor m_dataProcessor;
?
?public void service(ServiceManager sm) throws ServiceException {
????? m_dataProcessor = (IDataProcessor) sm.lookup("DataProcessor");
?}
?
?public void Execute() {
??? ........
??? string input = m_dataProcessor.ProcessData(input);
??? ........
?}
}
在Avalon的模式中,ServiceManager扮演著一個容器,設(shè)計者可以透過程序或組態(tài)文件,將特定的對象,如DummyDataProcessor推到容器中,接下來InputAccept就只需要詢問容器來取得對象即可,在這種模式下,InputAccept不需再撰寫Inject方法,主程序也可以藉由ServiceManager,解開與DummyDataProcessor的依賴關(guān)系。使用Container時有一個特質(zhì),就是Injection動作是由Conatiner來自動完成的,這是Dependency Injection的重點之一。
PS:在正確的Interface Injection定義中,組裝InputAccept與IDataProcessor的是容器,在本例中,我并未使用容器,而是提取其行為。
2-3、Constructor Injection
Constructor Injection意指構(gòu)造函數(shù)注入,主要是利用構(gòu)造函數(shù)參數(shù)來注入依賴關(guān)系,構(gòu)造函數(shù)注入通常是與容器緊密相關(guān)的,容器允許設(shè)計者透過特定方法,將欲注入的對象事先放入容器中,當(dāng)使用端要求一個支持構(gòu)造函數(shù)注入的對象時,容器中會依據(jù)目標對象的構(gòu)造函數(shù)參數(shù),一一將已放入容器中的對象注入。程序6是一個簡單的容器類別,其支持Constructor Injection。
程序6
public static class Container{
??? private static Dictionary<Type, object> _stores = null;
??? private static Dictionary<Type, object> Stores
??? {
??????? get
??????? {
??????????? if (_stores == null)
??????????????? _stores = new Dictionary<Type, object>();
??????????? return _stores;
??????? }
??? }
??? private static Dictionary<string, object> CreateConstructorParameter(Type targetType)
??? {
??????? Dictionary<string, object> paramArray = new Dictionary<string, object>();
??????? ConstructorInfo[] cis = targetType.GetConstructors();
??????? if (cis.Length > 1)
??????????? throw new Exception("target object has more then one constructor,container can't peek one for you.");
??????? foreach (ParameterInfo pi in cis[0].GetParameters())
??????? {
??????????? if (Stores.ContainsKey(pi.ParameterType))
??????????????? paramArray.Add(pi.Name, GetInstance(pi.ParameterType));
??????? }
??????? return paramArray;
??? }
??? public static object GetInstance(Type t)
??? {
??????? if (Stores.ContainsKey(t))
??????? {
??????????? ConstructorInfo[] cis = t.GetConstructors();
??????????? if (cis.Length != 0)
??????????? {
??????????????? Dictionary<string, object> paramArray = CreateConstructorParameter(t);
??????????????? List<object> cArray = new List<object>();
??????????????? foreach (ParameterInfo pi in cis[0].GetParameters())
??????????????? {
??????????????????? if (paramArray.ContainsKey(pi.Name))
??????????????????????? cArray.Add(paramArray[pi.Name]);
??????????????????? else
??????????????????????? cArray.Add(null);
??????????????? }
??????????????? return cis[0].Invoke(cArray.ToArray());
??????????? }
??????????? else if (Stores[t] != null)
??????????????? return Stores[t];
??????????? else
??????????????? return Activator.CreateInstance(t, false);
??????? }
??????? return Activator.CreateInstance(t, false);
??? }
??? public static void RegisterImplement(Type t, object impl)
??? {
??????? if (Stores.ContainsKey(t))
??????????? Stores[t] = impl;
??????? else
??????????? Stores.Add(t, impl);
??? }
??? public static void RegisterImplement(Type t)
??? {
??????? if (!Stores.ContainsKey(t))
??????????? Stores.Add(t, null);
??? }
}
Container類別提供了兩個方法,RegisterImplement有兩個重載方法,一接受一個Type對象及一個不具型物件,它會將傳入的Type及對象成對的放入Stores這個Collection中,另一個重載方法則只接受一個Type對象,調(diào)用這個方法代表調(diào)用端不預(yù)先建立該對象,交由GetInstance方法來建立。GetInstance方法負責(zé)建立對象,當(dāng)要求的對象類型存在于Stores記錄中時,其會取得該類型的構(gòu)造函數(shù),并依據(jù)構(gòu)造函數(shù)的參數(shù),一一調(diào)用GetInstance方法來建立對象。程序7是使用這個Container的范例。
程序7
class Program{
??? static void Main(string[] args)
??? {
??????? Container.RegisterImplement(typeof(InputAccept));
??????? Container.RegisterImplement(typeof(IDataProcessor), new PromptDataProcessor());
??????? InputAccept accept = (InputAccept)Container.GetInstance(typeof(InputAccept));
??????? accept.Execute();
??????? Console.Read();
??? }
}
public class InputAccept
{
??? private IDataProcessor _dataProcessor;
??? public void Execute()
??? {
??????? Console.Write("Please Input some words:");
??????? string input = Console.ReadLine();
??????? input = _dataProcessor.ProcessData(input);
??????? Console.WriteLine(input);
??? }
??? public InputAccept(IDataProcessor dataProcessor)
??? {
??????? _dataProcessor = dataProcessor;
??? }
}
public interface IDataProcessor
{
??? string ProcessData(string input);
}
public class DummyDataProcessor : IDataProcessor
{
??? #region IDataProcessor Members
??? public string ProcessData(string input)
??? {
??????? return input;
??? }
??? #endregion
}
public class PromptDataProcessor : IDataProcessor
{
??? #region IDataProcessor Members
??? public string ProcessData(string input)
??? {
??????? return "your input is: " + input;
??? }
??? #endregion
}
2-4、Setter Injection
Setter Injection意指設(shè)值注入,主要概念是透過屬性的途徑,將依賴對象注入目標對象中,與Constructor Injection模式一樣,這個模式同樣需要容器的支持,程序8是支持Setter Injection的Container程序行表。
程序8
public static class Container{
??? private static Dictionary<Type, object> _stores = null;
??? private static Dictionary<Type, object> Stores
??? {
??????? get
??????? {
??????????? if (_stores == null)
??????????????? _stores = new Dictionary<Type, object>();
??????????? return _stores;
??????? }
??? }
??? public static object GetInstance(Type t)
??? {
??????? if (Stores.ContainsKey(t))
??????? {
??????????? if (Stores[t] == null)
??????????? {
??????????????? object target = Activator.CreateInstance(t, false);
??????????????? foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(target))
??????????????? {
??????????????????? if (Stores.ContainsKey(pd.PropertyType))
??????????????????????? pd.SetValue(target, GetInstance(pd.PropertyType));
??????????????? }
??????????????? return target;
??????????? }
??????????? else
??????????????? return Stores[t];
??????? }
??????? return Activator.CreateInstance(t, false);
??? }
??? public static void RegisterImplement(Type t, object impl)
??? {
??????? if (Stores.ContainsKey(t))
??????????? Stores[t] = impl;
??????? else
??????????? Stores.Add(t, impl);
??? }
??? public static void RegisterImplement(Type t)
??? {
??????? if (!Stores.ContainsKey(t))
??????????? Stores.Add(t, null);
??? }
}
程序代碼與Constructor Injection模式大致相同,兩者差異之處僅在于Constructor Injection是使用構(gòu)造函數(shù)來注入,Setter Injection是使用屬性來注入,程序9是使用此Container的范例。
程序9
class Program{
??? static void Main(string[] args)
??? {
??????? Container.RegisterImplement(typeof(InputAccept));
??????? Container.RegisterImplement(typeof(IDataProcessor), new PromptDataProcessor());
??????? InputAccept accept = (InputAccept)Container.GetInstance(typeof(InputAccept));
??????? accept.Execute();
??????? Console.Read();
??? }
}
public class InputAccept
{
??? private IDataProcessor _dataProcessor;
??? public IDataProcessor DataProcessor
??? {
??????? get
??????? {
??????????? return _dataProcessor;
??????? }
??????? set
??????? {
??????????? _dataProcessor = value;
??????? }
??? }
??? public void Execute()
??? {
??????? Console.Write("Please Input some words:");
??????? string input = Console.ReadLine();
??????? input = _dataProcessor.ProcessData(input);
??????? Console.WriteLine(input);
??? }
}
2-5、Service Locator
在Martain Fowler的文章中,Dependency Injection并不是唯一可以將對象依賴關(guān)系降低的方式,另一種Service Locator架構(gòu)也可以達到同樣的效果,從架構(gòu)角度來看,Service Locator是一個服務(wù)中心,設(shè)計者預(yù)先將Servcie對象推入Locator容器中,在這個容器內(nèi),Service是以Key/Value方式存在。欲使用該Service對象的對象,必須將依賴關(guān)系建立在Service Locator上,也就是說,不是透過構(gòu)造函數(shù)、屬性、或是方法來取得依賴對象,而是透過Service Locator來取得。
本文轉(zhuǎn)自:http://www.cnblogs.com/zhenyulu/articles/641728.html
E文地址:http://www.martinfowler.com/articles/injection.html
轉(zhuǎn)載于:https://www.cnblogs.com/xray2005/archive/2009/07/28/1532908.html
總結(jié)
- 上一篇: C# Winform只能输入数字的Tex
- 下一篇: 聊聊我对开发项目选技术的看法