Object Builder Application Block文/黃忠成2006/9/21一、IoC 簡介IoC的全名是『Inversion of Control』,字面上的意思是『控制反轉』,要了解這個名詞的真正含意,得從『控制』這個詞切入。一般來說,當設計師撰寫一個Console程式時,控制權是在該程式上,她決定著何時該印出訊息、何時又該接受使用者輸入、何時該進行資料處理,如程式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(); } } }
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不同,當程式2被執行後,控制權其實並不在此程式中,而是在底層的Windows Forms Framework上,當此程式執行後,控制權會在Application.Run函式呼叫後,由主程式轉移到Windows Forms Framework上,進入等待訊息的狀態,當使用者按下了Form上的按鈕後,底層的Windows Forms Framework會收到一個訊息,接著會依照訊息來呼叫button1_Click函式,此時控制權就由Windows Forms Framework轉移到了主程式。程式2充份演譯了『控制反轉』的意含,也就是將原本位於主程式中的控制權,反轉到了Windows Forms Framework上。 二、Dependency InjectionIoC的中心思想在於控制權的反轉,這個概念於現今的Framework中相當常見,.NET Framework中就有許多這樣的例子,問題是!既然這個概念已經實作於許多Framework中,那為何近年來IoC會於社群引起這麼多的討論?著名的IoC實作體如Avalon、Spring又達到了什麼目的呢?就筆者的認知,IoC是一個廣泛的概念,主要中心思想就在於控制權的反轉,Windows Forms Framework與Spring在IoC的大概念下,都可以算是IoC的實作體,兩者不同之處在於究竟反轉了那一部份的控制權,Windows Forms Framework將主程式的控制權反轉到了自身上,Spring則是將物件的建立、釋放、配置等控制權反轉到自身,雖然兩者都符合IoC的大概念,但設計初衷及欲達成的目的完全不同,因此用IoC來統稱兩者,就顯得有些籠統及模糊。設計大師Martin Fowler針對Spring這類型IoC實作體提出了一個新的名詞『Dependency Injection』,字面上的意思是『依賴注入』。對筆者而言,這個名詞比起IoC更能描述現今許多宣稱支援IoC的Framework內部的行為,在Martin Fowler的解釋中, Dependency Injection分成三種,一是Interface Injection(介面注射)、Constructor Injection(建構子注射)、Setter Injection(設值注射)。2-1、Why we need Dependency Injection?OK,花了許多篇幅在解釋IoC與Dependency Injection兩個概念,希望讀者們已經明白這兩個名詞的涵意,在切入Dependency Injection這個主題前,我們要先談談為何要使用Dependency 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 } }
這是一個簡單且無用的例子,但卻可以告訴我們為何要使用Dependency Injection,在這個例子中,必須在建立InputAccept物件時傳入一個實作IDataProcessor介面的物件,這是Interface Base Programming概念的設計模式,這樣做的目的是為了降低InputAccept與實作體間的耦合關係,重用InputAccept的執行流程,以此來增加程式的延展性。那這個設計有何不當之處呢?沒有!問題不在InputAccept、IDataProcessor的設計,而在於使用的方式。
InputAccept accept = new InputAccept(new PromptDataProcessor());
//declare public 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物件,當GenericFactory.Create函式被呼叫時,她會查詢所擁有的Factory物件對應表,這個對應表是以type of base class/type of factory成對的格式存放,程式必須在一啟動時準備好這個對應表,這可以透過組態檔或是程式碼來完成,GenericFactory.Create函式在找到所傳入的type of base class所對應的type of factory後,就建立該Factory的實體,然後呼叫該Factory物件的Create函式來建立IDataProcessor物件實體後回傳。另外,為了統一Factory的呼叫方式,GenericFactory要求所有註冊的Factory物件必須實作IFactory介面,此介面只有一個需要實作的函式:Create。方便讀者易於理解這個設計概念,圖1以流程圖呈現這個設計的。圖1
public static class Container { private static Dictionary _stores = null; private static Dictionary Stores { get { if (_stores == null) _stores = new Dictionary(); return _stores; } } private static Dictionary CreateConstructorParameter(Type targetType) { Dictionary paramArray = new Dictionary(); 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 paramArray = CreateConstructorParameter(t); List