Saturday, March 17, 2007

Object Builder Application Block

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();

}

}

}

從整個流程上看來,OS將控制權交給了此程式,接下來就看此程式何時將控制權交回,這是Console模式的標準處理流程。程式1演譯了『控制』這個字的意思,那麼『反轉』這個詞的涵意呢?這可以用一個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不同,當程式2被執行後,控制權其實並不在此程式中,而是在底層的Windows Forms Framework上,當此程式執行後,控制權會在Application.Run函式呼叫後,由主程式轉移到Windows Forms Framework上,進入等待訊息的狀態,當使用者按下了Form上的按鈕後,底層的Windows Forms Framework會收到一個訊息,接著會依照訊息來呼叫button1_Click函式,此時控制權就由Windows Forms Framework轉移到了主程式。程式2充份演譯了『控制反轉』的意含,也就是將原本位於主程式中的控制權,反轉到了Windows Forms Framework上。


二、Dependency Injection


IoC的中心思想在於控制權的反轉,這個概念於現今的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());

使用InputAccept時,必須在建立物件時傳入一個實作IDataProcess介面的物件,此處直接建立一個PromptDataProcessor物件傳入,這使得主程式與PromptDataProcessor物件產生了關聯性,間接的摧毀使用IDataProcessor時所帶來的低耦合性,那要如何解決這個問題呢?讀過Design Patterns的讀者會提出以Builder、Factory等樣式解決這個問題,如下所示。

//Factory

InputAccept accept = new InputAccept(DataProcessorFactory.Create());

//Builder

InputAccept accept = new InputAccept(DataProcessorBulder.Build());

兩者的實際流程大致相同,DataProcessorFactory.Create函式會依據組態檔的設定來建立指定的IDataProcessor實作體,回傳後指定給InputAccept,DataProcessBuilder.Build函式所做的事也大致相同。這樣的設計是將原本位於主程式中IDataProcessor物件的建立動作,轉移到DataProcessorFactory、DataProcessorBuilder上,這也算是一種IoC觀念的實現,只是這種轉移同時也將主程式與IDataProcessor物件間的關聯,平移成主程式與DataProcessorFactory間的關聯,當需要建立的物件一多時,問題又將回到原點,程式中一定會充斥著AFactory、BFactory等Factory物件。徹底將關聯性降到最低的方法很簡單,就是設計Factory的Factory、或是Builder的Builder,如下所示。

//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


那這樣的設計有何優勢?很明顯的,這個設計已經將主程式與DataProcessorFactory關聯切除,轉移成主程式與GenericFactory的關聯,由於只使用一個Factory:GenericFactory,所以不存在於AFactory、BFactory這類問題。這樣的設計概念確實降低了物件間的關聯性,但仍然不夠完善,因為有時物件的建構子會需要一個以上的參數,但GenericFactory卻未提供途徑來傳入這些參數(想像當InputAccept也是經由GenericFactory建立時),當然!我們可以運用object[]、params等途徑來傳入這些參數,只是這麼做的後果是,主程式會與實體物件的建構子產生關聯,也就是間接的與實體物件產生關聯。要切斷這層關聯,我們可以讓GenericFactory自動完成InputAccept與IDataProcessor實體物件間的關聯,也就是說在GenericFactory中,依據InputAccept的建構子宣告,取得參數型別,然後使用該參數型別(此例就是IDataProcessor)來呼叫GenericFactory.Create函式建立實體的物件,再將這個物件傳給InputAccept的建構子,這樣主程式就不會與InputAccept的建構子產生關聯,這就是Constructor Injection(建構子注入)的概念。以上的討論,我們可以理出幾個重點,一、Dependency Injection是用來降低主程式與物件間的關聯,二、Dependency Injection同時也能降低物件間的互聯性,三、Dependency Injection可以簡化物件的建立動作,進而讓物件更容易使用,試想!只要呼叫GenericFactory.Create(typeof(InputAccept))跟原先的設計,那個更容易使用?不過要擁有這些優點,我們得先擁有著一個完善的架構,這就是ObjectBuilder、Spring、Avalon等Framework出現的原因。

PS:這一小節進度超前許多,接下來將回歸Dependency Injection的三種模式,請注意!接下來幾小節的討論是依據三種模式的精神,所以例子以簡單易懂為主,不考慮本文所提及的完整架構。


2-2、Interface Injection



Interface Injection指的是將原本建構於物件間的依賴關係,轉移到一個介面上,程式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物件將一部份的動作轉移到另一個物件上,雖說如此,但InputAccept與該物件並未建立依賴關係,而是將依賴關係建立在一個介面:IDataProcessor上,經由一個函式傳入實體物件,我們將這種應用稱為Interface Injection。當然,如你所見,程式4的手法在實務應用上並未帶來太多的好處,原因是執行Interface Injection動作的仍然是主程式,這意味著與主程式與該物件間的依賴關係仍然存在,要將Interface Injection的概念發揮到極致的方式有兩個,一是使用組態檔,讓主程式由組態檔中讀入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扮演著一個容器,設計者可以透過程式或組態檔,將特定的物件,如DummyDataProcessor推到容器中,接下來InputAccept就只需要詢問容器來取得物件即可,在這種模式下,InputAccept不需再撰寫Inject函式,主程式也可以藉由ServiceManager,解開與DummyDataProcessor的依賴關係。使用Container時有一個特質,就是Injection動作是由Conatiner來自動完成的,這是Dependency Injection的重點之一。

PS:在正確的Interface Injection定義中,組裝InputAccept與IDataProcessor的是容器,在本例中,我並未使用容器,而是提取其行為。


2-3、Constructor Injection



Constructor Injection意指建構子注入,主要是利用建構子參數來注入依賴關係,建構子注入通常是與容器緊密相關的,容器允許設計者透過特定函式,將欲注入的物件事先放入容器中,當使用端要求一個支援建構子注入的物件時,容器中會依據目標物件的建構子參數,一一將已放入容器中的物件注入。程式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物件,呼叫這個函式代表呼叫端不預先建立該物件,交由GetInstance函式來建立。GetInstance函式負責建立物件,當要求的物件型別存在於Stores記錄中時,其會取得該型別的建構子,並依據建構子的參數,一一呼叫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意指設值注入,主要概念是透過屬性的途徑,將依賴物件注入目標物件中,與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是使用建構子來注入,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並不是唯一可以將物件依賴關係降低的方式,另一種Service Locator架構也可以達到同樣的效果,從架構角度來看,Service Locator是一個服務中心,設計者預先將Servcie物件推入Locator容器中,在這個容器內,Service是以Key/Value方式存在。欲使用該Service物件的物件,必須將依賴關係建立在Service Locator上,也就是說,不是透過建構子、屬性、或是方法來取得依賴物件,而是透過Service Locator來取得。


三、ObjectBuilder Application Block



ObjectBuilder一開始出現於Microsoft所提出的Composite UI Application Block,主司物件的建立及釋放工作,她實現了本文前面所提及的Dependency Injection概念,同時在架構上提供了高度的延展性。運用ObjectBuilder來建立物件,設計師可以透過程式或組態檔,對物件建立與釋放的流程進行細部的調整,例如改變物件建立時所呼叫的Constructor(建構子),調整傳入的參數,於物件建立後呼叫特定函式等等。鑑於ObjectBuilder的功能逐漸完整,加上社群對於Dependency Injection實作體的強烈需求,Microsoft正式將ObjectBuilder納入Enterprise Library 2006中,並修改Caching、Logger、Security、Data Access等Application Block的底層,令其於ObjectBuilder整合,以此增加這些Application Block的延展性。就官方文件的說明,ObjectBuilder Application Block提供以下的功能。

表1

  • 允許要求一個抽象物件或介面,ObjectBuilder會依據程式或組態檔的設定,傳回一個實體物件。
  • 回傳一個既存物件,或是每次回傳一個新的物件(多半用於Dependency、Singleton情況,稍後會有詳細說明)。
  • 透過特定的Factory建立一個物件,這個Factory可以依據組態檔的設定來建立物件(CustomFactory,隸屬於Enterprise Common Library)。
  • 當物件擁有一個以上的建構子時,依據已有的參數,自動選取相容的建構子來建立要求的物件。(Consturctor Injection)
  • 允許物件於建立後,透過程式或組態檔來賦值至屬性,或是呼叫特定的函式。(Setter Injection、Interface Injection)
  • 提供一組Attribute,讓設計師可以指定需要Injection的屬性,亦或是於物件建立後需要呼叫的函式,也就是使用Reflection來自動完成Injection動作。
  • 提供IBuilerAware介面,實作此介面的物件,ObjectBuilder會於建立該物件後,呼叫OnBuildUp或是OnTearDown函式。
  • 提供TearDown機制,按建立物件的流程,反向釋放物件。

對於多數讀者來說,這些官方說明相當的隱誨,本文嘗試由架構角度切入,討論ObjectBuidler的主要核心概念,再透過實作讓讀者們了解,該如何使用ObjectBuidler。


3-1、The Architecture of Object Builder


圖2


圖2是ObjectBuilder中四個主要核心物件的示意圖,BuidlerContext是一個概念型的環境物件,在這個物件中,包含著一組Strategys物件,一組Polices物件,一個Locator物件, ObjectBuidler採用Strategys Pipeline(策略流)概念,設計師必須透過Strategy串列來建立物件,而Strategy會透過Polices來尋找『型別/id』對應的Policy物件,使用她來協助建立指定的物件。此處有一個必須特別提出來討論的概念,Strategy在架構上是與型別無關的,每個BuidlerContext會擁有一群Strategys物件,我們透過這個Strategys物件來建立任何型別的物件,不管建立的物件型別為何,都會通過這個Strategys Pipeline。這意味著,當我們希望於建立A型別物件後呼叫函式A1,於建立B型別物件後呼叫函式 B1時,負責呼叫函式的Strategy物件會需要一個機制來判別該呼叫那個函式,那就是Policy物件,BuilderContext中擁有一個Polices物件,其中存放著與『型別/id』對應的Policy物件,如圖3所示。

圖3


值得一提的是,Policy是以Type/id方式,也就是『型別/id』方式來存放,這種做法不只可以讓不同型別擁有各自的Policy,也允許同型別但不同id擁有各自的Policy。ObjectBuilder中的最後一個元素是Locator,Locator物件在ObjectBuidler中扮演著前述的Service Locator角色,設計師可以用Key/Value的方式,將物件推入Locator中,稍後再以Key值來取出使用。


3-2、Strategys



ObjectBuilder內建了許多Strategy,這些Strategy可以大略分成四種類型,如圖4。

圖4



Pre-Creation Strategy



Pre-Creation意指物件被建立前的初始動作,參與此階段的Strategy有:TypeMappingStrategy、PropertyReflectionStrategy、ConstructorReflectionStrategy、MethodReflectionStrategy及SingletonStrategy,稍後我們會一一檢視她們。


Creation Strategy



Creation類型的Strategy主要工作在於建立物件,她會利用Pre-Creation Strategys所準備的參數來建立物件,ObjectBuilder就是運用Pre-Creation的ConstructorReflectionStrategy及CreationStrategy來完成Constructor Injection動作。


Initialization Strategy



當物件建立後,會進入初始化階段,這就是Initialization Strategy階段,在此階段中,PropertySetterStrategy會與PropertyReflectionStrategy合作,完成Setter Injection。而MethodExecutionStrategy則會與MethodReflectionStrategy合作,在物件建立後,呼叫特定的函式,也就是Method Injection(視使用方式,Interface Injection是以此種方式完成的)。


Post-Initialization Strategy



在物件建立並完成初始化動作後,就進入了Post-Initialization Strategy階段,在此階段中,BuilderAwareStrategy會探詢已建立的物件是否實作了IBuilderAware介面,是的話就呼叫IBuilderAware.OnBuildUp函式。


關於物件釋放



先前曾經提過,ObjectBuidler在建立物件時,會一一呼叫所有Strategy來建立物件,同樣的!當釋放物件時,ObjectBuilder也會進行同樣的動作,不過方向是相反的,在內建的Strategy中,只有BuilderAwareStrategy會參與物件釋放的動作,在物件釋放時,BuilderAwareStrategy會探詢欲釋放的物件是否實作了IBuidlerAware介面,是的話就呼叫IBuidlerAware.OnTearDown函式。


3-3、A Simple Application



再怎麼詳細的說明,少了一個實例就很難讓人理解,本節以一個簡單的ObjectBuidler應用實例開始,一步步帶領讀者進入ObjectBuilder的世界。

程式10

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace SimpleApp

{


class
Program

{


static
void Main(string[] args)

{


Builder builder = new
Builder();


TestObject obj = builder.BuildUp<TestObject>(new
Locator(), null, null);

obj.SayHello();


Console.ReadLine();

}

}



public
class
TestObject

{


public
void SayHello()

{


Console.WriteLine("TEST");

}

}

}

這是一個相當陽春的例子,在程式一開始時建立了一個Builder物件,她是ObjectBuilder所提供的Facade物件,其會預先建立一般常用的Strategy串列,並於BuilderUp函式被呼叫時,建立一個BuilderContext物件,並將Srategy串列及Polices串列指定給該BuilderContext,然後進行物件的建立工作。


How Object Creating



要了解前面的例子中,TestObject物件究竟是如何被建立起來的,首先必須深入Builder物件的建構動作。

private
StrategyList<TStageEnum> strategies = new
StrategyList<TStageEnum>();


public BuilderBase()

{

}


public
PolicyList Policies

{

get { return policies; }

}


public
StrategyList<TStageEnum> Strategies

{

get { return strategies; }

}


public Builder(IBuilderConfigurator<BuilderStage> configurator)

{

Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);

Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);

Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);

Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);


Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());


if (configurator != null)

configurator.ApplyConfiguration(this);

}

當Buidler物件被建立時,其建構子會將前面所提及的幾個Strategys加到Strategies這個StrategyList Collection物件中,待BuildUp函式被呼叫時指定給新建立的BuilderContext物件。

public TTypeToBuild BuildUp<TTypeToBuild>(IReadWriteLocator locator,

string idToBuild, object existing, params
PolicyList[] transientPolicies)

{

return (TTypeToBuild)BuildUp(locator, typeof(TTypeToBuild), idToBuild, existing, transientPolicies);

}


public
virtual
object BuildUp(IReadWriteLocator locator, Type typeToBuild,

string idToBuild, object existing, params
PolicyList[] transientPolicies)

{

....................

return DoBuildUp(locator, typeToBuild, idToBuild, existing, transientPolicies);

...................

}


private
object DoBuildUp(IReadWriteLocator locator, Type typeToBuild, string idToBuild, object existing,

PolicyList[] transientPolicies)

{

IBuilderStrategyChain chain = strategies.MakeStrategyChain();

..............

IBuilderContext context = MakeContext(chain, locator, transientPolicies);

..........................

object result = chain.Head.BuildUp(context, typeToBuild, existing, idToBuild);

.......................

}


private
IBuilderContext MakeContext(IBuilderStrategyChain chain,

IReadWriteLocator locator, params
PolicyList[] transientPolicies)

{

.............

return
new
BuilderContext(chain, locator, policies);

}

當Builder的泛型函式BuildUp函式被呼叫後,其會呼叫非泛型的BuildUp函式,該函式會呼叫DoBuildUp函式,此處會透過strategies(先前於Builder建構子時初始化的StrategyList物件)來取得Strategys串列,並指定給稍後由MakeContext函式建立的BuilderContext,最後呼叫Strategy串列中第一個Strategy的BuildUp函式來進行物件的建立動作。在這一連串的動作中,我們可以釐清幾個容易令人混淆的設計,第一!我們是透過Strategy串列,也就是IBuidlerStrategyChain.Head.BuildUp來建立物件,這個Head屬性就是Strategy串列中的第一個Strategy。第二!BuilderContext的作用在於,於呼叫各個Strategy.BuildUp函式時,給予她們存取此次建立動作所使用的Strategys及Policies等物件的機會。


Policy物件的用途



現在,我們弄清楚了Strategy的用途,BuilderContext的真正涵意,但還有兩個元素尚未釐清,其中之一就是Policy物件,前面曾經稍微提過,Strategy是與型別無關的設計概念,因此為了針對不同型別做個別的處理,我們需要另一個與型別相關的設計,那就是Policy物件,要確認這點,必須重返Builder的建構子。

public Builder(IBuilderConfigurator<BuilderStage> configurator)

{

..................

Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());

.................

}

這裡呼叫了Policies的SetDefault函式,Policies是一個PolicyList物件,其提供了推入(Set、SetDefault)及取出(Get)函式,允許設計者針對所有『型別/id』及特定『型別/id』指定對應的IBuilderPolicy物件,那這有什麼用呢?這個問題可以由CreationStrategy類別中的以下這段程式碼來回答。

public
override
object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)

{

if (existing != null)

BuildUpExistingObject(context, typeToBuild, existing, idToBuild);

else

existing = BuildUpNewObject(context, typeToBuild, existing, idToBuild);

return
base.BuildUp(context, typeToBuild, existing, idToBuild);

}


[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]

private
object BuildUpNewObject(IBuilderContext context, Type typeToBuild,

object existing, string idToBuild)

{

ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);

.........................

InitializeObject(context, existing, idToBuild, policy);

return existing;

}


private
void InitializeObject(IBuilderContext context, object existing, string id, ICreationPolicy policy)

{

................

ConstructorInfo constructor = policy.SelectConstructor(context, type, id);

...................

object[] parms = policy.GetParameters(context, type, id, constructor);

...............

method.Invoke(existing, parms);

}

如你所見,CreationStrategy於建立物件時,會由Policies中取出『型別/id』對應的ICreationPolicy物件,接著利用她來取得ConstructorInfo(建構子函式),再以GetParameters函式來取得建構子所需的參數,最後呼叫此建構子。這段程式碼告訴我們Policy的真正用途,就是用來協助Strategy於不同『型別/id』物件建立時,採取不同的動作,這也就是說,Strategy與Policy通常是成對出現的。


Locator



最後一個尚未釐清的關鍵元素是Locator,我們於呼叫Builder的BuildUp函式時,建立了一個Locator物件並傳入該函式,這是用來做什麼的呢?在ObjectBuilder中,Locator扮演兩種角色,第一個角色是提供一個物件容器供Strategy使用,這點可以透過以下程式了解。

public
class
SingletonStrategy : BuilderStrategy

{

public
override
object BuildUp(IBuilderContext context, Type typeToBuild,

object existing, string idToBuild)

{

DependencyResolutionLocatorKey key = new
DependencyResolutionLocatorKey(

typeToBuild, idToBuild);

if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))

{

TraceBuildUp(context, typeToBuild, idToBuild, "");

return context.Locator.Get(key);

}

return
base.BuildUp(context, typeToBuild, existing, idToBuild);

}

}

SingletonStrategy是一個用來維持某一個物件只能有一份實體存在,當此Strategy被喚起時,其會先至Locator尋找目前要求的物件是否已被建立,是的話就取出該物件並傳回。Locator同時也可以作為一個Service Locator,這點可以由以下程式碼來驗證。

locator.Add("Test",new
TestObject());

.............

TestObject obj = locator.Get<TestObject>("Test");

當然,這種手法有一個問題,那就是TestObject物件是預先建立後放在Locator中,這並不是一個好的設計,後面的章節我們會提出將Service Locator與Dependency Injection整合的手法。

PS:ObjectBuidlerLocator離完善的Service Locator還有段距離。


四、Dependency Injection With ObjectBuilder



ObjectBuilder支援Dependency Injection中定義的三種Injection模式,本章將一一介紹如何運用ObjectBuilder來實現。


4-1、Constructor Injection



Constructor Injection的精神在於使用建構子來進行注入動作,本節延用InputAccept的例子,程式11是改採ObjectBuilder進行Constructor Injection的例子。

程式11

using System;

using System.Collections.Generic;

using System.Text;

using System.Configuration;

using Microsoft.Practices.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;


namespace OB_ConstructorInjectionTest

{


class
Program

{


static
void UseValueParameter(MyBuilderContext context)

{


ConstructorPolicy creationPolicy = new
ConstructorPolicy();

creationPolicy.AddParameter(new
ValueParameter(typeof(IDataProcessor),

new
PromptDataProcessor()));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);

}



static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext(new
Locator());

context.InnerChain.Add(new
CreationStrategy());

UseValueParameter(context);


InputAccept accept = (InputAccept)context.HeadOfChain.BuildUp(context,

typeof(InputAccept), null, null);

accept.Execute();


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



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

}

}

程式於一開始時,建立了一個MyBuilderContext物件,會自行建立BuilderContext物件而不使用Builder物件的目的很單純,就是為了釐清個別Strategy究竟做了那些事,這點在使用Builder物件時,會因為內建的Strategy都已加入,而顯得有些模糊。在MyBuilderContext物件建立後,此處將一個CreationStrategy加到Strategy串列中,CreationStrategy這個Strategy被歸類為Creation階段,是真正建立物件的Strategy,緊接著UseValueParameter函式會被呼叫,這個函式中建立了一個ConstructorPolicy物件,並呼叫其AddParameter函式,加入一個ValueParameter物件,這個ValueParameter物件就對應著InputAccept的建構子所需的參數,CreationStrategy於物件建立後,會透過BuilderContext的Policies來取得『型別/id』對應的ICreationPolicy物件(本例就是ConstructorPolicy物件),然後呼叫ICreationPolicy.SelectionConstructor函式,這個函式必須根據呼叫者已用ICreationPolicy.AddParameter所傳入的參數來選擇正確的建構子,然後再呼叫這個建構子並填入參數值來完成物件建立工作,圖5是這整個流程的示意圖。

圖5


圖中讀者可能會有所迷惑的是,FormatterServices.GetSafeUninitializedObject函式是何作用?這是.NET Framework中一個建立物件的途徑,與一般new或是Activator.CreateInstance方式不同,GetSafeUninitializedObject函式並不會觸發該物件的建構子,只是單純的將物件建立起來而已,因此CreationStrategy才必須於最後呼叫對應的建構子。


Understanding Parameter



程式11中使用了一個ValueParameter物件,要知道這個物件的作用,我們得先了解Parameter在ObjectBuilder中所代表的意義,在三種注入模式中,有一個共通的規則,就是需要有參數來注入,Constructor Injection是透過建構子參數注入,而Interface Injection則是透過函數參數注入,Setter Injection則是透過屬性注入,因此參數是這三種注入模式都會用到的觀念,所以ObjectBuilder定義了IParameter介面,並提供一組實作此介面的參數物件,於注入時期由這些參數物件來取得參數值,如圖6。

圖6



ValueParameter



這是一個最簡單的Paramter物件,建構子如下所示:

public ValueParameter(Type valueType, object value)

她的GetValue函式僅是將建構子傳入的value物件傳回而已。


DependencyParamter



DependencyParameter是一個功能強大的Parameter物件,程式12是以DependencyParameter來取代ValueParameter完成Constructor Injection的例子。

程式12

static
void UseDependencyParameter(MyBuilderContext context)

{


ConstructorPolicy creationPolicy = new
ConstructorPolicy();

creationPolicy.AddParameter(new
DependencyParameter(typeof(IDataProcessor),null,

typeof(PromptDataProcessor),NotPresentBehavior.CreateNew,SearchMode.Local));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);



ConstructorPolicy creationPolicy2 = new
ConstructorPolicy();

context.Policies.Set<ICreationPolicy>(creationPolicy2, typeof(PromptDataProcessor),null);

}

讀者可以發現,DependencyParameter並未要求建構者傳入任何物件實體,而是要求建構者傳入注入時對應的參數型別、參數名稱、實體型別、NotPersentBehavoir及SearchMode等參數,下面的程式列表是DependencyParameter的建構子:

public DependencyParameter(Type parameterType, string name,

Type createType, NotPresentBehavior notPresentBehavior, SearchMode searchMode)

第一個參數是參數的型別,第二個參數是參數的名稱,當ConstructorPolicy於SelectConstructor函式時,會依據這兩個參數來選取適合的建構子,第三個參數是實體物件的型別,以本例來說,就是以PromptDataProcessor這個型別建立物件來傳入需要IDataProcessor型別的建構子、函式或屬性,第四個參數則影響了DependencyParameter的取值動作,預設情況下,DependencyParameter會先至Locator中取值,這個動作會受到第五個參數:SearchMode的影響(稍後會介紹這一部份),如果找不到的話,就會依據此參數值來做動作,NotPersentBehavior這個列舉的定義如下:

public
enum
NotPresentBehavior

{

CreateNew,

ReturnNull,

Throw,

}

CreateNew代表著當DependencyParameter於Locator找不到需要的值時,呼叫BuilderContext.HeadOfChain.BuildUp函式來建立該物件,以此例來說即是如此,所建立物件的型別就是PromptDataProcessor。ReturnNull則是回傳一個Null值,Throw則是直接拋出一個例外。好了,了解了整體流程後,現在讓我們一一釐清這個流程中剩下的部份,第一!於Locator找尋需要的值是什麼意思,試想一種情況,當我們在做Dependency Injection時,是否有某些欲注入物件是可重用的,也就是該物件可以只建立一個,注入多個不同的物件,讓這些物件共用這個注入物件,這就是DependencyParameter會先至Locator中找尋已推入的注入物件的原因,請參考程式13的例子。

程式13

static
void UseDependencyParameter(MyBuilderContext context)

{

context.InnerLocator.Add(new
DependencyResolutionLocatorKey(typeof(IDataProcessor), null),

new
PromptDataProcessor());



ConstructorPolicy creationPolicy = new
ConstructorPolicy();

creationPolicy.AddParameter(new
DependencyParameter(typeof(IDataProcessor),

null,typeof(PromptDataProcessor),NotPresentBehavior.CreateNew,SearchMode.Local));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);



ConstructorPolicy creationPolicy2 = new
ConstructorPolicy();

context.Policies.Set<ICreationPolicy>(creationPolicy2, typeof(PromptDataProcessor),null);

}

這個例子預先建立了一個PromptDataProcessor物件,並以DependencyResolutionLocatorKey封裝後推入Locator中,這樣一來,當DependencyParameter取值時,就會依據參數的『型別/id』至Locator找尋需要的值,此時就會得到我們所推入的PromptDataProcessor物件,而不是建立一個新的,另外!只要於AddParameter所傳入的DependencyParameter是以IDataProcessor為參數型別,並以null為id(名稱)的話,那麼永遠都會傳回我們所推入Locator的PromptDataProcessor物件。第二個要釐清的是SearchMode的涵意,在ObjectBuilder的架構上,Locator是可以有Parent/Child關係的,當DependencyParameter要找尋需要的物件時,如果SearchMode是Local的話,那麼這個搜尋動作只會搜尋該Locator自身,如果是Up的話,那麼在該Locator自身搜尋不到時,就會往Parent Locator搜尋。第三個要釐清的是第二個ConstructorPolicy的建立動作,還記得嗎?我們提過Policy是『型別/id』相關的,當DependencyParameter無法於Locator找到需要的物件而透過BuildUp來建立物件時,該『型別/id』同樣需要一個ICreationPolicy來對應,否則將會引發Missing Policy的例外,注意!DependencyParameter所使用的name參數必須與設定Set<ICreationPolicy>時所傳入的第三個參數相同。最後一個問題是,如果每個『型別/id』都要設定對應的ICreationPolicy,豈不累人,ObjectBuilder當然沒有這麼不人性化,我們可以呼叫Policies.SetDefault來為所有『型別/id』預設一個ICreationPolicy,如程式14所示。

程式14

static
void UseDependencyParameter(MyBuilderContext context)

{


ConstructorPolicy creationPolicy = new
ConstructorPolicy();

creationPolicy.AddParameter(new
DependencyParameter(typeof(IDataProcessor),

null,typeof(PromptDataProcessor),NotPresentBehavior.CreateNew,SearchMode.Local));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);


context.Policies.SetDefault<ICreationPolicy>(new
ConstructorPolicy());

}


CreationParameter



與DependencyParameter相同,CreationParameter也會透過BuildUp來建立物件,不同的是其不會先搜尋Locator,也無法作參數型別與實體型別對應,因此無法適用於InputAccept這種以介面為介質的注入方式,必須與TypeMappingStrategy(後述)合用才能解決,如程式15所示。

程式15

static
void UseCreationParameter(MyBuilderContext context)

{


ConstructorPolicy creationPolicy = new
ConstructorPolicy();

creationPolicy.AddParameter(new
CreationParameter(typeof(IDataProcessor)));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);



TypeMappingPolicy mappingPolicy = new
TypeMappingPolicy(typeof(PromptDataProcessor), null);

context.Policies.Set<ITypeMappingPolicy>(mappingPolicy, typeof(IDataProcessor), null);


context.Policies.SetDefault<ICreationPolicy>(new
ConstructorPolicy());

}


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext(new
Locator());

context.InnerChain.Add(new
TypeMappingStrategy());

context.InnerChain.Add(new
CreationStrategy());

UseCreationParameter(context);


InputAccept accept = (InputAccept)context.HeadOfChain.BuildUp(context,

typeof(InputAccept), null, null);

accept.Execute();


Console.Read();

}


CloneParameter



CloneParameter的建構子接受一個IParameter參數,當其GetValue函式被呼叫時,會透過從建構子指定的Parameter物件來取值,如果取得的值是實作了ICloneable介面的物件時,其將呼叫Clone函式來拷貝該值,否則傳回原值,下面的程式片斷是CloneParametr的建構子宣告。

public CloneParameter(IParameter param)


LookupParameter



LookupParameter的建構子接受一個object型別的參數,當GetValue函式被呼叫時,會經由Locator.Get函式,以建構子所傳入的參數為鍵值,取得位於Locator中的值,下面的程式片斷為LookupParameter的建構子宣告。

public LookupParameter(object key)

程式16則是將InputAccept範例改為使用LookupParameter的版本。

程式16

static
void UseLookupParameter(MyBuilderContext context)

{

context.InnerLocator.Add("dataProcessor", new
PromptDataProcessor());


ConstructorPolicy creationPolicy = new
ConstructorPolicy();


creationPolicy.AddParameter(new
LookupParameter("dataProcessor"));

context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);

context.Policies.SetDefault<ICreationPolicy>(new
ConstructorPolicy());

}


InjectionConstructor Attribute



使用Paramerer物件來進行Consturctor Injection時,設計者必須在建立物件前,預先準備這些Parameter物件,雖然動作不算繁鎖,但若全部物件的建立都要這麼做,未免有些沒有效率,為此!ObjectBuilder提供了另一種較為簡單的方法,就是利用InjectionConstructor這個Attribute,再搭配上ConstructorReflectionStrategy物件,自動的為設計者準備這些Parmeter物件,程式17是修改為InjectionConstructor模式的版本。

程式17

static
void UseInjectionConstructorAttribute(MyBuilderContext context)

{


context.InnerChain.Add(new
ConstructorReflectionStrategy());

context.InnerChain.Add(new
CreationStrategy());

}


..........

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);

}



[InjectionConstructor]


public InputAccept([Dependency(Name="dataProcessor",

CreateType=typeof(PromptDataProcessor))]IDataProcessor dataProcessor)

{

_dataProcessor = dataProcessor;

}

}

要使用InjectionConstructor Attribute,我們必須在CreationStrategy這個Strategy前加入一個ConstructorReflectionStrategy物件,她會於建立物件動作時,探詢欲建立物件型別所提供的所有建構子,選取已標上InjectionConstrucor Attribute的那個為指定建構子,接著ConstructorReflectionStrategy會探詢該建構子的所有參數,查看是否標上Dependency Attribute,是的話就以其設定建立DependencyParameter,否則建立一個新的DependencyParameter,她會單以型別參數來建立DependencyParameter,最後ConstructorReflectionStrategy會以這些資訊來建立對應的ConstructorPolicy物件,完成整個物件建立動作。


Understanding Dependency Attribute



ConstructorReflectionStrategy依賴兩個關鍵的Attribute,一個是用來標示指定建構子的InjectionConstructor Attribute,另一個則是用來標示參數該如何取得的Dependency Attribute,此Attribute有四個屬性,分別對應到DependencyParameter的四個屬性,如表2。

表2

DependencyAttribute

DependencyParameter

說明

Name

Name

id(名稱)

CreateType

CreateType

欲建立物件的實體型別

NotPersentBehavior

NotPersentBehavior

當欲建立物件無法由Locator取得時的行為模式。

SearchMode

SearchMode

Locator的搜尋法則。

使用Dependency Attribute與ConsturctorReflectionStrategy模式的優點是設計者不需花費時間一一建立Parameter物件,而缺點就是CreateType參數,由於ConstructorReflectionStrategy依賴著Dependency Attribute的CreateType參數來決定實際建立物件的型別,這使得設計者必須在標示Dependency Attribute時,一併指定這個參數,否則ConstructorReflectionStrategy將會以參數型別做為建立實際物件時的型別,而在本例中,我們無法建立一個IDataProcessor物件,這點降低了程式的可訂制性。那這要如何解決呢?簡單的方法是撰寫一個新的Dependency Attribute、或是使用TypeMappingStrategy,複雜的則是撰寫一個新的ConstructorReflectionStrategy,後面的章節我們會再重訪這個問題。


Injection with DependencyResolutionLocator



前面談到DependencyParameter時曾經提過,她會先至Locator中搜尋需要的參數值,那麼這也意味著,在使用ConstructorReflectionStrategy時,我們可以將參數值先行推入Locator中,這樣就可以避開指定CreateType了,如程式18所示。

程式18

static
void UseDependencyResolution(MyBuilderContext context)

{

context.InnerChain.Add(new
ConstructorReflectionStrategy());

context.InnerChain.Add(new
CreationStrategy());


context.InnerLocator.Add(new
DependencyResolutionLocatorKey(typeof(IDataProcessor),

"dataProcessor"),new
PromptDataProcessor());

}


[InjectionConstructor]

public InputAccept([Dependency(Name="dataProcessor")]IDataProcessor dataProcessor)

當然,這仍然會有一個問題,那就是必須預先建立PromptDataProcessor物件,而非於InputAccept物件建立時期建立,這是在不撰寫自定Dependency Attribute或Strategy,亦或是使用TypeMappingStrategy情況下的簡易解法。


DefaultCreationPolicy and ConstructorPolicy



ObjectBuilder內建了兩個ICreationPolicy的實作體,一是前面所使用的ConsturoctPolicy,二是DefaultCreationPolicy,與ConstructorPolicy不同,DefaultCationPolicy永遠使用預設的建構子,如下所示。

public
ConstructorInfo SelectConstructor(IBuilderContext context, Type type, string id)

{

if (constructor != null)

return constructor;


List<Type> types = new
List<Type>();

foreach (IParameter parm in parameters)

types.Add(parm.GetParameterType(context));


return type.GetConstructor(types.ToArray());

}

而呼叫該建構子時所需的參數,則直接以BuildUp函式,依據參數的『型別/id』來建立,沒有與Parameter的互動。

public
object[] GetParameters(IBuilderContext context, Type type, string id, ConstructorInfo constructor)

{

ParameterInfo[] parms = constructor.GetParameters();

object[] parmsValueArray = new
object[parms.Length];


for (int i = 0; i < parms.Length; ++i)

parmsValueArray[i] = context.HeadOfChain.BuildUp(context, parms[i].ParameterType, null, id);


return parmsValueArray;

}

由此可見,DefaultCreationPolicy有兩個特色,一是其會選擇頂端的建構子,二是其一律以BuidUp函式依據參數型別來建立參數物件,不需要設計者介入。那在何種情況下選擇DefaultCreationPolicy呢?一般來說,使用ConstructorPolicy時,因為其會依據設計者所加入的Parameter物件來選擇建構子,如果設計者未準備這些,那麼ConstructorPolicy將因無法取得適合的建構子而引發例外,雖然這點可以經由搭配ConstructorReflectionStrategy來解決,但使用ConstructorReflectionStrategy時必須搭配Dependency Attribtue及InjectionConstructor Attribute,所以也是個負擔。使用DefaultCreationPolicy就沒有這些問題了,缺點則是無法指定實際建立的參數物件型別,所以DefautlCreationPolicy通常被設定成預設的ICreationPolicy,主要作用在於當我們所建立的物件是簡單的,只有一個建構子,且不需要特別指定參數實際型別時,就交由她來處理,而需要特別處理的,就運用『型別/id』對應的ConstructorPolicy或是Dependency Attribute、Injection Constructor Attrbute搭配ConstructorReflectionStrategy來處理。


4-2、Interface Injection



Interface Injection在ObjectBuidler中可以經由Method Injection來完成,指的是在物件建立後,呼叫所指定的函式來完成初始化動作,而負責這個工作的就是MethodExecutionStrategy,本節持續延應InputAccept來示範如何於ObjectBuidler中實現Interface Injection。


MethodExecutionStrategy



要實現Interface Injection,除了必須使用CreationStrategy來建立物件外,還要使用另一個Strategy:MethodExecutionStrategy,她會在物件建立完成後,執行指定的函式,程式19是使用MethodExecutionStrategy來實現Interface Injection的例子。

程式19

static
void UseMethodInfo(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());


context.InnerChain.Add(new
MethodExecutionStrategy());


IMethodCallInfo
callInfo = new
MethodCallInfo("SetDataProcessor",

new
ValueParameter(typeof(IDataProcessor), new
PromptDataProcessor()));


IMethodPolicy policy = new
MethodPolicy();

policy.Methods.Add("SetDataProcessor", callInfo);

context.Policies.Set<IMethodPolicy>(policy, typeof(InputAccept), null);

}


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext(new
Locator());

UseDependencyAttribute(context);

context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());


InputAccept accept = (InputAccept)context.HeadOfChain.BuildUp(context, typeof(InputAccept),

null, null);

accept.Execute();


Console.Read();

}


public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



public
void SetDataProcessor(IDataProcessor dataProcessor)

{

_dataProcessor = dataProcessor;

}



public
void Execute()

{


Console.Write("Please Input some words:");


string input = Console.ReadLine();

input = _dataProcessor.ProcessData(input);


Console.WriteLine(input);

}

}

此處使用ValueParameter來進行呼叫指定函式時的參數注入動作,在使用MethodExecutionStrategy時,設計者必須先行建立呼叫函式時所需的MethodCallInfo物件,這是一個實作IMethodInfo介面的物件,設計者必須於此物件中指定欲呼叫的函式、及傳入的Parameter物件,下面是MethodInfo的建構子宣告。

public MethodCallInfo(string methodName)

public MethodCallInfo(string methodName, params
object[] parameters)

public MethodCallInfo(string methodName, params
IParameter[] parameters)

public MethodCallInfo(string methodName, IEnumerable<IParameter> parameters)

public MethodCallInfo(MethodInfo method)

public MethodCallInfo(MethodInfo method, params
IParameter[] parameters)

public MethodCallInfo(MethodInfo method, IEnumerable<IParameter> parameters)

MethodInfo擁有許多重載的建構子,大概分成兩大類:函式名稱及MethodInfo物件,每類會分成四個,分別是無參數、使用params傳入參數值、使用params傳入IParamete物件、傳入IEnumerable<IParameter>物件。在MethodInfo物件建立後,接著就要將這些物件傳入IMethodPolicy物件,並指定給context.Policies物件,這樣就完成了Interface Injection的準備動作,之後建立InputAccept物件後,SetDataProcess函式就會被呼叫,同時會傳入指定的PromptDataProcessor物件。


How MethodExecutionStrategy Working?



當MethodExecutionStrategy的BuildUp函式被呼叫時,會透過context.Policies來取得型別對應的IMethodPolicy物件,如下所示。

IMethodPolicy policy = context.Policies.Get<IMethodPolicy>(type, id);

然後會透過IMethodPolicy物件來取得所有需要處理的IMethodCallInfo物件,並一一呼叫其SelectMethod函式來取得欲呼叫函式,如下所示。

MethodInfo methodInfo = methodCallInfo.SelectMethod(context, type, id);

SelectMethod函式會依據當初建立此IMethodCallInfo物件時所指定的函式名稱、參數數量及型別來取得對應函式的MethodInfo物件。於取得MethodInfo物件後,緊接著就是透過IMethodCallInfo.GetParameters函式來取得呼叫此函式時需傳入的參數值,如下所示。

object[] parameters = methodCallInfo.GetParameters(context, type, id, methodInfo);

最後呼叫MethodInfo.Invoke函式來呼叫該函式就完成整個動作了。

methodInfo.Invoke(obj, parameters);

好了,這就是MethodExecutionStrategy的整個流程,現在我們要釐清幾個可能會令人困惑的問題,第一!當欲呼叫的函式是重載,有多個同名函式時,SelectMethod依據什麼來決定要呼叫那一個?答案是參數數量及型別。第二!當使用Parameter物件傳入MethodCallInfo物件的建構子時,GetParameters函式會透過Parameter.GetValue來取值,那麼當直接以object[]方式傳入MethodCallInfo的建構子時呢?答案是該建構子會逐個為傳入的object建立ValueParameter物件,如下所示。

public MethodCallInfo(string methodName, params
object[] parameters)

:this(methodName, null, ObjectsToIParameters(parameters))

{

}


private
static
IEnumerable<IParameter> ObjectsToIParameters(object[] parameters)

{

List<IParameter> results = new
List<IParameter>();

if (parameters != null)

foreach (object parameter in parameters)

results.Add(new
ValueParameter(parameter.GetType(), parameter));

return results.ToArray();

}

最後一個問題是,可以進行一個以上的函式呼叫嗎?答案是可以,建立對應的MethodCallInfo物件,並加到IMethodPolicy後即可,呼叫的順序則是依照MethodCallInfo加入IMethodPolicy的順序。。


Use DependencyParameter



與Constructor Injection相同,你也可以使用DependencyParameter來進行Interface Injection動作,如程式20。

程式20

static
void UseDependencyParameter(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
MethodExecutionStrategy());



MethodCallInfo callInfo = new
MethodCallInfo("SetDataProcessor",

new
DependencyParameter(typeof(IDataProcessor), "dataProcessor", typeof(PromptDataProcessor), NotPresentBehavior.CreateNew, SearchMode.Local));


IMethodPolicy policy = new
MethodPolicy();

policy.Methods.Add("SetDataProcessor", callInfo);

context.Policies.Set<IMethodPolicy>(policy, typeof(InputAccept), null);

}


use MethodReflectionStrategy



如同ConstructorReflectionStrategy的作用一樣,ObjectBuilder也提供了供Method Injection使用的MethodReflectionStrategy物件,要使用她,我們必須為欲進行Method Injection的函式標上InjectionMethod Attribute,如程式21所示。

程式21

static
void UseDependencyResolverLocator(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
MethodReflectionStrategy());


context.InnerChain.Add(new
MethodExecutionStrategy());


context.InnerLocator.Add(new
DependencyResolutionLocatorKey(typeof(IDataProcessor),

"dataProcessor"), new
PromptDataProcessor());

}


public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



[InjectionMethod]


public
void SetDataProcessor(

[Dependency(Name="dataProcessor"))]IDataProcessor dataProcessor)

{

_dataProcessor = dataProcessor;

}

...........

}

本例使用DependencyResolutionLocatorKey模式進行注入動作,有了Constructor Injection部份的解說,相信讀者對這種模式已經了然於胸了。


Injection with Dependency Attribute and CreateType



同樣的,我們也可以在Dependency Attribute中指定CreateType來達到同樣的效果,如程式22所示。

程式22

static
void UseDependencyAttribute(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
MethodReflectionStrategy());

context.InnerChain.Add(new
MethodExecutionStrategy());

}


public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



[InjectionMethod]


public
void SetDataProcessor(

[Dependency(Name="dataProcessor",

CreateType=typeof(PromptDataProcessor))]IDataProcessor dataProcessor)

{

_dataProcessor = dataProcessor;

}

.............

}


4-3、Setter Injection



ObjectBuilder使用PropertySetterStrategy來進行Setter Injection,用法與前述的Interface Injection模式大致相同,如程式23所示。

程式23

static
void UsePropertySetter(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());


context.InnerChain.Add(new
PropertySetterStrategy());


PropertySetterPolicy policy = new
PropertySetterPolicy();

policy.Properties.Add("DataProcessor", new
PropertySetterInfo("DataProcessor",

new
ValueParameter(typeof(IDataProcessor), new
PromptDataProcessor())));

context.Policies.Set<IPropertySetterPolicy>(policy, typeof(InputAccept), null);

}


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext(new
Locator());

UsePropertySetter(context);

context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());


InputAccept accept = (InputAccept)context.HeadOfChain.BuildUp(context,

typeof(InputAccept), null, null);

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);

}

}

設計者必須預先建立PropertySetterInfo物件,並為其指定欲設定的屬性名稱及參數,PropertySetterInfo是一個實作了IPropertySetterInfo介面的物件,其建構子宣告如下。

public PropertySetterInfo(string name, IParameter value)

public PropertySetterInfo(PropertyInfo propInfo, IParameter value)

有了MethodCallInfo的經驗,讀者們對這些建構子應該不會有任何疑惑,應該會抱怨其不像MethodCallInfo般提供那麼多的選擇吧(笑)。在ProeprtySetterInfo建立後,接著只要將其加到IPropertySetterPolicy物件中,並依『型別/id』指定給context.Policies即可完成Setter Injection。


How PropertySetterStrategy Work?



當PropertySetterStrategy的BuildUp函式被呼叫時,會透過context.Policies來取得型別對應的IPropertySetterPolicy物件,如下所示。

IPropertySetterPolicy policy = context.Policies.Get<IPropertySetterPolicy>(type, id);

然後會透過IMethodPoliIPropertySetterPolicyy物件來取得所有需要處理的IPropertySetterInfo物件,並一一呼叫其SelectProperty函式來取得欲設定的屬性,如下所示。

PropertyInfo propInfo = propSetterInfo.SelectProperty(context, type, id);

SelectProperty函式會依據當初建立此IPropertySetterInfo物件時所指定的屬性名稱、參數來取得對應屬性的PropertyInfo物件。於取得PropertyInfo物件後,緊接著就是透過IPropertySetterInfo.GetValue函式來取得設定此屬性時需傳入的值,如下所示。

object value = propSetterInfo.GetValue(context, type, id, propInfo);

最後呼叫PropertyInfo.SetValue函式來設定屬性值就完成整個動作了。

propInfo.SetValue(obj, value, null);

這就是整個Setter Injection的流程,這裡只有一個問題,我們可以設定一個以上的屬性嗎?答案是肯定的,只要建立對應數量的PropertySetterInfo物件即可。


use DependencyParameter


同樣的,使用DependencyParameter也可以達到同樣的效果,如程式24。

程式24

static
void UseDependencyParameter(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
PropertySetterStrategy());


PropertySetterPolicy policy = new
PropertySetterPolicy();

policy.Properties.Add("DataProcessor", new
PropertySetterInfo("DataProcessor",


new
DependencyParameter(typeof(IDataProcessor),"DataProcessor",

typeof(PromptDataProcessor),NotPresentBehavior.CreateNew,SearchMode.Local)));

context.Policies.Set<IPropertySetterPolicy>(policy, typeof(InputAccept), null);

}


use PropertyReflectionStrategy



相對於ConsturctorReflectionStrategy及MethodReflectionStrategy,ObjectBuilder也提供了一個同類型的PropertyReflectionStrategy,我們可以搭配Dependency Attribute及DependencyResolutionLocatorKey物件來達到同樣效果,如程式25。

程式25

static
void UseDependencyResolutionLocator(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
PropertyReflectionStrategy());

context.InnerChain.Add(new
PropertySetterStrategy());


context.Locator.Add(new
DependencyResolutionLocatorKey(typeof(IDataProcessor),

"DataProcessor"), new
PromptDataProcessor());

}


public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



[Dependency(Name="DataProcessor")]


public
IDataProcessor DataProcessor

{


get

{


return _dataProcessor;

}


set

{

_dataProcessor = value;

}

}

.........

}


Injection with Dependency Attribute and CreateType



我們也可以使用Dependency Attribute及CreateType參數來進行Setter Injection,如程式26。

程式26

static
void UseDependencyAttribute(MyBuilderContext context)

{

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
PropertyReflectionStrategy());

context.InnerChain.Add(new
PropertySetterStrategy());

}


public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



[Dependency(Name="DataProcessor",CreateType=typeof(PromptDataProcessor))]


public
IDataProcessor DataProcessor

{


get

{


return _dataProcessor;

}


set

{

_dataProcessor = value;

}

}

...............

}


五、Misc


5-1、SingletonStrategy



SingletonStrategy可於物件實體首次建立後,將實體保留在Context中的Locator內的ILifetimeContainer物件中,之後相同型態、id相同的物件建立動作,都是傳回這個物件,這是Singleton模式的實現,如程式27。

程式27

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_SingletonTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
SingletonStrategy());

context.InnerChain.Add(new
CreationStrategy());

context.Policies.Set<ISingletonPolicy>(new
SingletonPolicy(true), typeof(TestObject), null);

context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());



TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context,

typeof(TestObject), null, null);


TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context,

typeof(TestObject), null, null);


if (obj1 == obj2)


Console.WriteLine("Singleton");


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
class
TestObject

{

}

}

要將一個『型別/id』標示為Singleton,設計者必須於Strategy串列中加入SingletonStrategy物件,並建立一個SingletonPolicy物件,這是一個實作了ISingletonPolicy介面的類別,其建構子如下。

public SingletonPolicy(bool isSingleton);

CreatationStrategy在建立物件後,會從context.Policies中取出『型別/id』對應的ISingletonPolicy物件,以其IsSingleton屬性來決定建立的物件是否為Singleton模式,是的話就將該物件實體填入ILifetimeContainer中,同時以DependencyResolutionLocatorKey包裝該物件實體,放入Locator中,如下所示。

private
void RegisterObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)

{

if (context.Locator != null)

{

ILifetimeContainer lifetime = context.Locator.Get<ILifetimeContainer>(

typeof(ILifetimeContainer), SearchMode.Local);


if (lifetime != null)

{

ISingletonPolicy singletonPolicy = context.Policies.Get<ISingletonPolicy>(

typeToBuild, idToBuild);


if (singletonPolicy != null && singletonPolicy.IsSingleton)

{

context.Locator.Add(new
DependencyResolutionLocatorKey(

typeToBuild, idToBuild), existing);

lifetime.Add(existing);

......

}

}

}

}

以上流程是當該物件實體尚未建立時的流程,假如以BuildUp建立的物件已經存在於Locator中,那麼SingletonStrategy的BuildUp函式將直接傳回Locator中的物件實體。

public
override
object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)

{

DependencyResolutionLocatorKey key = new
DependencyResolutionLocatorKey(

typeToBuild, idToBuild);


if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))

{

TraceBuildUp(context, typeToBuild, idToBuild, "");

return context.Locator.Get(key);

}

return
base.BuildUp(context, typeToBuild, existing, idToBuild);

}


PS:注意,SingletonStrategy在該物件已經存在於Locator中時,是直接回傳,並不會呼叫後面如MethodExecutionStrategy、PropertySetterStrategy等Strategy。


5-2、TypeMappingStrategy



前面的章節早已使用過TypeMappingStrategy這個物件了,她主要負責『型別/id』的對應,例如將IDataProcessor介面型別的建立,替換成PromptDataProcessor型別,如程式28所示。

程式28

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_TypeMappingTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
TypeMappingStrategy());

context.InnerChain.Add(new
CreationStrategy());


ITypeMappingPolicy policy = new
TypeMappingPolicy(typeof(TestObject),null);

context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);

context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());


ITestInterface obj1 = (ITestInterface)context.HeadOfChain.BuildUp(

context, typeof(ITestInterface), null, null);

obj1.SayHello();


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
interface
ITestInterface

{


void SayHello();

}



public
class
TestObject : ITestInterface

{


public
void SayHello()

{


Console.WriteLine("TEST");

}

}

}

TypeMappingStrategy必須搭配TypeMappingPolicy物件使用,TypeMappingPolicy是一個實作ITypeMappingPolicy介面的物件,建構子宣告如下。

public TypeMappingPolicy(Type type, string id)

第一個參數是映射的實體型別,以本例來說就是TestObject,第二個參數是識別id,接著將其加入context.Policies中,如下所示。

context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null)

當TypeMappingStrategy的BuildUp函式被呼叫時,她會以『型別/id』取得對應的ITypeMappingPolicy物件,透過她來取得對應的型別,之後將使用這個型別呼叫下一個Strategy的BuildUp函式,這就是Type Mapping的流程。

PS:注意,Type Mapping型別必須相容,如介面->實作、基礎類別->衍生類別。


5-3、BuildAwareStrategy



BuildAwareStrategy可以於實作IBuilderAware介面物件建立或釋放時,呼叫對應的OnBuildUp或OnTearDown函式,如程式29所示。

程式29

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_BuildAwareTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
BuilderAwareStrategy());


context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());



TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,

typeof(TestObject), null, null);

context.HeadOfChain.TearDown(context, obj);


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
class
TestObject : IBuilderAware

{

#region IBuilderAware Members



public
void OnBuiltUp(string id)

{


Console.WriteLine("Object is build up");

}



public
void OnTearingDown()

{


Console.WriteLine("Object is TearDown");

}


#endregion

}

}

與其它的Strategy物件不同,BuilderAwareStrategy並不需要Policy物件的協助,她只是判斷建立的物件是否實作了IBuilderAware介面。


5-4、BuildUp的第三、四個參數



截至目前為止,我們的例子在呼叫BuildUp函式時,第三及四個參數都傳入null,這兩個參數的用途究竟為何呢?這要先從BuildUp函式的宣告談起。

object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild);

當我們於呼叫BuildUp函式指定existing為一物件實體時,CreationStrategy將不會建立任何新的物件,只會進行Singleton模式物件的相關動作,然後就呼叫下一個Strategy物件的BuildUp函式,簡單的說!在CreationStrategy後的Strategy仍然會運行,例如Method Injection、Setter Injection都會再次運行,程式30可以協助讀者理解這兩個參數的用途。

程式30

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_ExistingTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
CreationStrategy());



ConstructorPolicy policy = new
ConstructorPolicy(new
ValueParameter(typeof(string),"id"));

context.Policies.Set<ICreationPolicy>(policy, typeof(TestObject), null);



TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,

typeof(TestObject), null, null);


TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context,

typeof(TestObject), obj, null);



if (obj == obj2)


Console.WriteLine("is same object.");


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
class
TestObject

{


private
string _id;



public
string ID

{


get

{


return _id;

}

}



public TestObject(string id)

{

_id = id;

}

}

}

BuildUp的第四個參數則主導著ObjectBuilder的型別識別及物件識別機制,請先看程式31的例子。

程式31

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_IDTesting

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
CreationStrategy());

context.InnerChain.Add(new
PropertySetterStrategy());


context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());



PropertySetterInfo pi1 = new
PropertySetterInfo("ID", new
ValueParameter<string>("ID1"));


PropertySetterPolicy pp1 = new
PropertySetterPolicy();

pp1.Properties.Add("ID", pi1);

context.Policies.Set<IPropertySetterPolicy>(pp1, typeof(TestObject), "TO1");




PropertySetterInfo pi2 = new
PropertySetterInfo("ID", new
ValueParameter<string>("ID2"));


PropertySetterPolicy pp2 = new
PropertySetterPolicy();

pp2.Properties.Add("ID", pi2);

context.Policies.Set<IPropertySetterPolicy>(pp2, typeof(TestObject), "TO2");



TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),

null, "TO1");


TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),

null, "TO2");



Console.WriteLine(obj1.ID);


Console.WriteLine(obj2.ID);


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
class
TestObject

{


public
string _id;



public
string ID

{


get

{


return _id;

}


set

{

_id = value;

}

}

}

}

在這個例子中,我們建立了兩個PropertySetterPolicy物件,分別以ID2、ID2為id加到了context.Policies中,當CreationStrategy建立物件時,她是以下面的程式碼來取得對應的Policy物件。

private
object BuildUpNewObject(IBuilderContext context, Type typeToBuild, object existing,


string idToBuild)

{

ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);

..................

這段程式碼告訴我們一個重點,ObjectBuidler是以『型別/id』來做型別識別動作,也就是說TestObject+"ID1"、TestObject+"ID2"被ObjectBuilder視為兩個不同的物件建立動作,你可以分別為其設定專屬的Policy物件,也可以於呼叫BuildUp函式時,指定不同的id來建立同型別,但不同id的物件。另一個會使用『型別/id』來做識別的是DependencyResolutionLocatorKey物件,我們之前常使用她來完成Injection動作,而SingletonStrategy、DependencyParameter也都是運用她來完成所需完成的工作,其建構子如下所示。

public DependencyResolutionLocatorKey(Type type, string id)

這意味著,當我們使用SingletonStrategy時,可以利用『型別/id』來建立兩個同型別但不同id的Singleton物件,如程式32所示。

程式32

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_SingletonTwoTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilderContext context = new
MyBuilderContext();

context.InnerChain.Add(new
SingletonStrategy());

context.InnerChain.Add(new
CreationStrategy());

context.Policies.Set<ISingletonPolicy>(new
SingletonPolicy(true), typeof(TestObject), "ID1");

context.Policies.Set<ISingletonPolicy>(new
SingletonPolicy(true), typeof(TestObject), "ID2");

context.Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());



TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),

null, "ID1");


TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),

null, "ID2");


if (obj1 == obj2)


Console.WriteLine("Singleton");


Console.Read();

}

}



internal
class
MyBuilderContext : BuilderContext

{


public
IReadWriteLocator InnerLocator;


public
BuilderStrategyChain InnerChain = new
BuilderStrategyChain();


public
PolicyList InnerPolicies = new
PolicyList();


public
LifetimeContainer lifetimeContainer = new
LifetimeContainer();



public MyBuilderContext()

: this(new
Locator())

{

}



public MyBuilderContext(IReadWriteLocator locator)

{

InnerLocator = locator;

SetLocator(InnerLocator);

StrategyChain = InnerChain;

SetPolicies(InnerPolicies);



if (!Locator.Contains(typeof(ILifetimeContainer)))

Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);

}

}



public
class
TestObject

{

}

}

這個例子將TestObject+"ID1"、TestObject+"ID2"設定為兩個不同的Singleton物件,所以當首次建立並指定id時,所建立出來的兩個物件是相異的,也就是說,可以利用『型別/id』來建出兩個Singleton系統。


5-5、StrategyList




在本文一開始的範例中,我們使用Builder物件來建立物件,她使用了一個StrategyList物件來處理Strategy串列,這個物件提供了兩個重要的函式,一是MakeStrategyChain,她會將StrategyList中的Strategy輸出成BuilderStrategyChain物件,這是一個實作了IBuilderStrategyChain介面的物件,也是IBuilderContext所要求的Strategy串列物件。第二個函式是MakeReverseStrategyChain,她會將內含的Strategys反相排序後輸出成BuilderStrategyChain物件,這個動作是為了準備TearDown時所需的Strategy串列,還記得前面提過,TearDown的Strategy順序應該與建立時完全相反,這樣才能讓物件與其相關的子物件適當的釋放。


5-6、TStageEnum



StrategyList是一個泛型物件,她接受一個Enum型別,會依照Enum中所定義的元素來建立Strategy串列或是反相排序,要了解這個設計的原意,我們得先看看ObjectBuilder中所預定義,用來指定給StrategyList的列舉。

public
enum
BuilderStage

{

PreCreation,

Creation,

Initialization,

PostInitialization

}

讀者可以查覺,這與我們先前將Strategy分成四種類型的方式相呼應,StrategyList會依據PreCreation、Creation、Initialization、PostInitialization的順序來產生BuilderStrategyChain物件,這樣就不會因為錯置Strategy的順序,導致程式不正常(例如,先加入CreationStrategy再加入TypeMappingStrategy時,TypeMappingStrategy將無法運作)。Builder物件充份展示了BuilderStage與StrategyList的運用方式。

public Builder(IBuilderConfigurator<BuilderStage> configurator)

{

Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);

Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);

Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);

Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);

Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());

if (configurator != null)

configurator.ApplyConfiguration(this);

}

只要傳入的BuilderStage是正確的,不管TypeMappingStrategy是加在CreationStrategy前面或後面,皆可正常運作。不過同一類型的Strategy,但有順序需求的情況下,仍然要小心調整順序,程式32示範了運用BuilderStage所帶來的優點。

程式32

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_StrategyListTest

{


class
Program

{


static
void Main(string[] args)

{


MyBuilder builder = new
MyBuilder();


ITypeMappingPolicy policy = new
TypeMappingPolicy(typeof(TestObject), null);

builder.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);


ITestInterface obj1 = builder.BuildUp<ITestInterface>(new
Locator(), null, null);


Console.Read();

}

}



public
class
MyBuilder : BuilderBase<BuilderStage>

{


public MyBuilder()

: this(null)

{

}



public MyBuilder(IBuilderConfigurator<BuilderStage> configurator)

{

Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);

Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);

Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);

Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);

Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);


Policies.SetDefault<ICreationPolicy>(new
DefaultCreationPolicy());



if (configurator != null)

configurator.ApplyConfiguration(this);

}

}



public
interface
ITestInterface

{

}



public
class
TestObject : ITestInterface

{

}

}


5-6、PolicyList



BuilderContext所定義的Policies物件型別為PolicyList,PolicyList物件以Dictionary<BuilderPolicyKey, IBuilderPolicy>物件來儲存設計者所加入的Policy物件,其中用來作為鍵值的BuilderPolicyKey類別建構子如下。

public BuilderPolicyKey(Type policyType, Type typePolicyAppliesTo, string idPolicyAppliesTo)

第一個參數為policyType,也就是ICrationPolicy、ITypeMappingPolicy等之類,第二個參數是對應的型別,第三個參數則是id。設計者可以呼叫PolicyList.Set函式來加入一個Policy至內部的儲存體中,該函式會依據傳入的參數建立BuilderPolicyKey做為鍵值,然後將Policy加到Dictionary中,如下所示。

public
void Set(Type policyInterface, IBuilderPolicy policy, Type typePolicyAppliesTo,

string idPolicyAppliesTo)

{

BuilderPolicyKey key = new
BuilderPolicyKey(policyInterface,

typePolicyAppliesTo, idPolicyAppliesTo);

lock (lockObject)

{

policies[key] = policy;

}

}

另一個泛型類型的Set函式也可以達到同樣的效果。

public
void Set<TPolicyInterface>(TPolicyInterface policy,

Type typePolicyAppliesTo, string idPolicyAppliesTo)

where TPolicyInterface : IBuilderPolicy

{

Set(typeof(TPolicyInterface), policy, typePolicyAppliesTo, idPolicyAppliesTo);

}

設計者可以透過PolicyList.Get函式來取得對應的Policy物件,該函式如下所示。

public TPolicyInterface Get<TPolicyInterface>(Type typePolicyAppliesTo, string idPolicyAppliesTo)

where TPolicyInterface : IBuilderPolicy

{

return (TPolicyInterface)Get(typeof(TPolicyInterface), typePolicyAppliesTo, idPolicyAppliesTo);

}


public
IBuilderPolicy Get(Type policyInterface, Type typePolicyAppliesTo, string idPolicyAppliesTo)

{

BuilderPolicyKey key = new
BuilderPolicyKey(policyInterface,

typePolicyAppliesTo, idPolicyAppliesTo);

lock (lockObject)

{

IBuilderPolicy policy;


if (policies.TryGetValue(key, out policy))

return policy;

BuilderPolicyKey defaultKey = new
BuilderPolicyKey(policyInterface, null, null);

if (policies.TryGetValue(defaultKey, out policy))

return policy;

return
null;

}

}

SetDefault則可以用一個Policy來提供給所有型別使用,Get函式在找不到對應『型別/id』對應的Policy時,就會以該Policy回傳。


六、Locator



ObjectBuilder利用Locator物件來實現Service Locator,也利用Locator來進行Dependency Injection,在ObjectBuilder的架構上,Locator有兩種類型,一是Readonly Locator,顧名思義,這類Locator只允許讀取、不允許新增。二是ReadWriteLocator,她是允許新增、讀取類的Locator。我們可以從Visual Studio 2005的Class Diagram來觀察ObjectBuilder中的Locator階層架構。

圖7


6-1、Readonly Locator



ObjectBuidler定義了一個IReadableLocator介面,所有的Locator都必須直接或間接實作此介面,內建實作此介面的類別是ReadableLocator,她是一個抽象類別。真正完成實作可用的是ReadOnlyLocator,這個Locator只允許讀取,不允許新增。


6-2、ReadWrite Locator



ObjectBuilder中支援讀與寫的Locator是ReadWriterLocator,與ReadOnlyLocator一樣,她也是一個抽象類別,真正完成實作的是Locator類別。附帶一提,雖然Locator定義了蠻清楚的階層,但是BuilderContext只支援實作IReadWriterLocator介面的Locator。


6-3、WeakRefDictionary and Locator



Locator類別是我們一直都在使用的Locator,她是一個繼承自ReadWriterLocator的類別,值得一提的是,她使用一個WeakRefDictionary來儲存設計者所放入Locator的物件,WeakRefDictionary內部對於每個元素都會以WeakReference封裝,這意味著,Locator中的元素並無法保證一直都存在,因為CLR會在記憶體拮据時,先行釋放WeakRefernce所封裝的物件,這點讀者必須謹記。


七、Extending ObjectBuilder



ObjectBuilder除了支援三種Dependency Injection模式、Service Locator之外,最大的魅力應該來自於具高度延展性的架構,設計者可以透過撰寫Strategy、Policy、Locator等類別來參與物件的建立動作,本章以兩個範例來證明這點,一是EventSetterStrategy,她提供Event Injection功能,二是PoolStrategy,提供Pool模式的物件建立。


7-1、EventSetterStrategy



ObjectBuidler提供了Constructor Injection、Interface Injection(Method Ijection)、Setter Injection(Property Injection)三種Injection模式,雖然ObjectBuilder只提供了Propety式的Setter Injection,不過我們可以藉助於ObjectBuilder高度的延展性架構,讓ObjectBuidler也能支援Event Injection。


IEventSetterInfo



Event Injection與Property Injection同屬Setter Injection模式,兩者運作的模式也極為相似,ObjectBuilder在Property Injection部份是由ProperySeterInfo、PropertySetterPolicy及PropertySetterStrategy三個類別所構築而成,我們可以依循這個既定架構,實作Event Injection功能。首要必須定義一個IEventSetterInfo介面,這相對於IPropertySetterInfo介面之於Property Injection。

程式33

public
interface
IEventSetterInfo

{


object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo);


EventInfo SelectEvent(IBuilderContext context, Type type, string id);

}

IEventSetterInfo介面定義了兩個函式,SelectEvent函式是用來取得欲Injection事件的EventInfo物件,EventSetterStrategy會呼叫此函式來取得欲Injection事件的EventInfo物件,然後透過EventInfo.AddHandler來進行注入動作,這個注入動作所使用的值是透過呼叫IEventSetterInfo.GetValue函式來取得,此介面的實作程式碼如34。

程式34

public
sealed
class
EventSetterInfo : IEventSetterInfo

{


private
string _name = null;


private
IParameter _value = null;


#region IEventSetterInfo Members



public
object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo)

{


return _value.GetValue(context);

}



public
EventInfo SelectEvent(IBuilderContext context, Type type, string id)

{


return type.GetEvent(_name);

}


#endregion



public EventSetterInfo(string name,IParameter value)

{

_name = name;

_value = value;

}

}


IEventSetterPolicy



前面提過,Strategy是與型別無關的設計,因此需要Policy的協助,我們所設計的EventSetterStrategy也是一樣,Event Injection必須具備針對不同『型別/id』進行Event Injection的能力,所以必須設計一個IEventSetterPolicy介面,該介面必須直接或間接繼承自IBuilderPolicy介面,這是ObjectBuilder對於Policy的規範。

程式35

public
interface
IEventSetterPolicy : IBuilderPolicy

{


Dictionary<string, IEventSetterInfo> Events { get;}

}

針對同一『型別/id』物件可能需要注入一個以上的事件,此介面定義了一個Dictionary<string,IEventSetterInfo>物件,讓設計者可以指定一個以上的Event Injection動作,36是此介面的實作。

程式36

public
sealed
class
EventSetterPolicy : IEventSetterPolicy

{


private
Dictionary<string, IEventSetterInfo> _events = new
Dictionary<string, IEventSetterInfo>();

#region IEventPolicy Members



public
Dictionary<string, IEventSetterInfo> Events

{


get

{


return _events;

}

}


#endregion

}


EventSetterStrategy



完成了基礎類別的設計與實作後,剩下的就是Strategy,也就是EventSetterStrategy的設計與實作了,設計上,EventSetterStrategy只有一個任務,就是於BuildUp函式被呼叫時,透過『型別/id』經由context.Locator取得對應的IEventSetterPolicy物件,再透過她取得欲進行注入動作的IEventSetterInfo物件,接著呼叫IEventSetterInfo.SelectEvent函式取得EventInfo物件,最後呼叫IEventSetterInfo.GetValue取得欲注入的Event Handler物件,然後呼叫EventInfo.AddHandler函式完成注入動作。

程式37

public
class
EventSetterStrategy : BuilderStrategy

{


public
override
object BuildUp(IBuilderContext context, Type typeToBuild,

object existing, string idToBuild)

{


if (existing != null)

InjectEvents(context, existing, idToBuild);



return
base.BuildUp(context, typeToBuild, existing, idToBuild);

}



private
void InjectEvents(IBuilderContext context, object obj, string id)

{


if (obj == null)


return;



Type type = obj.GetType();


IEventSetterPolicy policy = context.Policies.Get<IEventSetterPolicy>(type, id);



if (policy == null)


return;



foreach (IEventSetterInfo eventSetterInfo in policy.Events.Values)

{


EventInfo eventInfo = eventSetterInfo.SelectEvent(context, type, id);



if (eventInfo != null)

{


if (TraceEnabled(context))

TraceBuildUp(context, type, id, "Event Setter", eventInfo.Name);



eventInfo.AddEventHandler(obj,

eventSetterInfo.GetValue(context, type, id, eventInfo) as
Delegate);

}

}

}

}


Testing



EventSetterStrategy的使用方式與PropertySetterStrategy相似,如38所示。

程式38

using System;

using System.ComponentModel;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace EventSetterTest

{


class
Program

{


static
void Main(string[] args)

{


Builder builder = new
Builder();

builder.Strategies.AddNew<EventSetterStrategy>(BuilderStage.Initialization);


IEventSetterPolicy policy = new
EventSetterPolicy();



EventHandler handler = new
EventHandler(CallHandler);

policy.Events.Add("Call", new
EventSetterInfo("Call",

new
ValueParameter(typeof(EventHandler), handler)));

builder.Policies.Set<IEventSetterPolicy>(policy, typeof(TestObject), null);



TestObject obj = builder.BuildUp<TestObject>(new
Locator(), null, null);

obj.RaiseCall();


Console.ReadLine();

}



static
void CallHandler(object sender, EventArgs args)

{


Console.WriteLine("Called");

}

}



public
class
TestObject

{


private
EventHandlerList _events = new
EventHandlerList();


private
static
object _onCall = new
object();



public
event
EventHandler Call

{


add

{

_events.AddHandler(_onCall, value);

}


remove

{

_events.RemoveHandler(_onCall, value);

}

}



protected
virtual
void OnCall(EventArgs args)

{


EventHandler handler = (EventHandler)_events[_onCall];


if (handler != null)

handler(this, args);

}



public
void RaiseCall()

{

OnCall(EventArgs.Empty);

}

}

}

圖8是此程式的運行結果。

圖8



7-2、PoolStrategy



GoF的書中,提出了三種物件管理Pattern,一是Singleton,意味著物件一旦建立後,就存放於某個儲存體中,之後所有要求物件的建立動作,都將會獲得同樣的物件實體,在ObjectBuilder中實現這個Pattern的就是SingletonStrategy。第二個Pattern是SingleCall模式,意味所有的物件建立動作都會獲得一個新的物件實體,跟new、create等語言所定義的物件建立模式相同,在Service模式中,SingleCall也意味著Service物件會在要求到達時建立,結束後就立即的釋放,這兩個模式都可以用ObjectBuilder輕易的實現。第三種Pattern就是Pool,也就是說在特定儲存體中維持一定數量的物件實體,當要求物件建立動作時,系統會遍尋儲存體中的物件,如果有物件標示為未使用狀態,那麼系統就回傳該物件,並將該物件標示為使用中,本節將實作一個PoolStrategy,讓ObjectBuilder可以具備Pool的能力。


PoolFactory



Pool Pattern的核心就是一個可以於儲存體中管理物件的能力,此處使用筆者書中所設計的PoolFactory類別來完成這個目的。

程式39

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace Orphean.WinFormHelper.Framework.Factorys

{


///
<summary>


/// a interface to be implement by Object Factory,


/// DAL use object factory to speed object constructing.


///
</summary>


public
interface
IObjectFactory

{


///
<summary>


/// acquire a object.


///
</summary>


///
<param name="type">object Type</param>


///
<returns>object</returns>


object AcquireObject(Type type);


///
<summary>


/// release a object.


///
</summary>


///
<param name="obj">a object to releasing</param>


void ReleaseObject(object obj);

}




public
sealed
class
PoolObjectFactory : IObjectFactory, IDisposable

{


class
PoolData

{


public
bool InUse = false;


public
object obj;

}



private
IList _storage;


private
int _max = 100;


private
bool _limit = false;


private
IBuilderContext _context = null;



public PoolObjectFactory(IBuilderContext context,int max, bool limit, IList storage):this(context)

{

_max = max;

_limit = limit;

_storage = storage;

}



public PoolObjectFactory(IBuilderContext context)

{

_context = context;

}



private
PoolData GetPoolData(object obj)

{


lock (_storage.SyncRoot)

{


for (int i = 0; i < _storage.Count; i++)

{


PoolData p = (PoolData)_storage[i];


if (p.obj == obj)


return p;

}

}


return
null;

}



private
object GetObject(Type type)

{


lock (_storage.SyncRoot)

{


if (_storage.Count > 0)

{


if (((PoolData)_storage[0]).obj.GetType() != type)


throw
new
Exception(


string.Format("the Pool Factory only for Type :{0}",

_storage[0].GetType().Name));

}



for (int i = 0; i < _storage.Count; i++)

{


PoolData p = (PoolData)_storage[i];


if (!p.InUse)

{

p.InUse = true;


return p.obj;

}

}



if (_storage.Count > _max && _limit)


throw
new
Exception("max limit is arrived.");



object obj = _context.HeadOfChain.BuildUp(_context, type, null, null);


PoolData p1 = new
PoolData();

p1.InUse = true;

p1.obj = obj;

_storage.Add(p1);


return obj;

}

}



private
void PutObject(object obj)

{


PoolData p = GetPoolData(obj);


if (p != null)

p.InUse = false;

}


#region IObjectFactory Members



public
object AcquireObject(Type type)

{


return GetObject(type);

}



public
void ReleaseObject(object obj)

{


if (_storage.Count > _max)

{


if (obj is
IDisposable)

((IDisposable)obj).Dispose();


PoolData p = GetPoolData(obj);


lock (_storage.SyncRoot)

_storage.Remove(p);


return;

}

PutObject(obj);

}


#endregion


#region IDisposable Members



public
void Dispose()

{


lock (_storage.SyncRoot)

{


for (int i = 0; i < _storage.Count; i++)

{


PoolData p = (PoolData)_storage[i];


if (p.obj is
IDisposable)

((IDisposable)p.obj).Dispose();

}

}

}


#endregion

}

}

本文的重點在於ObjectBuilder的應用與延伸,所以此處就不在贅述PoolFactory的實作細節。


IPoolPolicy



PoolStrategy在架構上與SingletonStrategy類似,我們必須設計一個IPoolPolicy介面,該介面的定義如程式40。

程式40

public
interface
IPoolPolicy : IBuilderPolicy

{


bool IsPool { get;}

}

此介面只定義了一個Pool屬性,用來告訴PoolStrategy那個『型別/id』是需要Pool,那個又是不需要的,雖然設計者可以針對要Pool的『型別/id』來指定IPoolPolicy,如果有特定物件不需要Pool動作,那就不指定IPoolPocy即可,但是我們無法排除一種情況,那就是系統裡大多數物件都需要Pool,僅有特定的物件不需要Pool,此時要特別對一個個物件設定IPoolPolicy的話,會相當的繁瑣。此時設計者可以以SetDefault來加入IPoolPolicy物件,將所有物件標示為可Pool,再針對不需要Pool的物件來指定IPoolPolicy。程式41是實作此介面的程式碼列表。

程式41

public
class
PoolPolicy : IPoolPolicy

{


private
bool _isPool = false;


#region IPoolPolicy Members



public
bool IsPool

{


get

{


return _isPool;

}

}


#endregion



public PoolPolicy(bool isPool)

{

_isPool = isPool;

}

}


PoolStrategy



PoolStrategy必須在BuildUp函式運用PoolFactory來取得要求的物件,在設計上,我們會為每個『型別/id』建立獨立的PoolFactory物件,這意味著每個『型別/id』的物件數量是獨立管理的。

程式42

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;

using Orphean.WinFormHelper.Framework.Factorys;


namespace OB_PoolStrategy

{


public
class
PoolStrategy:BuilderStrategy

{


private
WeakRefDictionary<object, object> _factoryMap =

new
WeakRefDictionary<object, object>();


private
bool _poolObjectCreating = false;



public
override
object BuildUp(IBuilderContext context, Type typeToBuild,

object existing, string idToBuild)

{


if (!_poolObjectCreating)

{


IPoolPolicy policy = context.Policies.Get<IPoolPolicy>(typeToBuild, idToBuild);


if (policy != null && policy.IsPool)

{


PoolLocatorKey key = new
PoolLocatorKey(typeToBuild, idToBuild);


PoolObjectFactory factory = null;


if (context.Locator.Contains(key))

{

factory = context.Locator.Get<PoolObjectFactory>(key);


lock (this)

{

_poolObjectCreating = true;


try

{

existing = factory.AcquireObject(typeToBuild);

}


finally

{

_poolObjectCreating = false;

}

}

}


else

{

factory = new
PoolObjectFactory(context, 15, false, new
ArrayList());

_poolObjectCreating = true;


try

{

existing = factory.AcquireObject(typeToBuild);

}


finally

{

_poolObjectCreating = false;

}


context.Locator.Add(key, factory);

}


if (!_factoryMap.ContainsKey(existing))

_factoryMap.Add(existing, factory);

}

}


return
base.BuildUp(context,typeToBuild,existing,idToBuild);

}



public
override
object TearDown(IBuilderContext context, object item)

{


if(_factoryMap.ContainsKey(item))

{


PoolObjectFactory factory = _factoryMap[item] as
PoolObjectFactory;


if(factory != null)

factory.ReleaseObject(item);

_factoryMap.Remove(item);

}


return
base.TearDown(context,item);

}

}



public
sealed
class
PoolLocatorKey

{


private
Type type;


private
string id;



public PoolLocatorKey()

: this(null, null)

{

}



public PoolLocatorKey(Type type, string id)

{


this.type = type;


this.id = id;

}



public
string ID

{


get { return id; }

}



public
Type Type

{


get { return type; }

}



public
override
bool Equals(object obj)

{


PoolLocatorKey other = obj as
PoolLocatorKey;



if (other == null)


return
false;



return (Equals(type, other.type) && Equals(id, other.id));

}




public
override
int GetHashCode()

{


int hashForType = type == null ? 0 : type.GetHashCode();


int hashForID = id == null ? 0 : id.GetHashCode();


return hashForType ^ hashForID;

}

}

}

在BuildUp函式被呼叫時,PoolStrategy會透過context.Policies取得『型別/id』對應的IPoolPolicy物件,判斷此次建立動作是否使用Pool,是的話就以『型別/id』至Locator中取出PoolFactory,如果Locator已經有該PoolFactory時,就直接呼叫PoolFactory.AcquireObject函式來取得物件實體,如果Locator中無對應的PoolFactory時,就建立一個並放入Locator中。在這個建立流程中有幾個重點,第一!我們將PoolFactory儲存在Locator中,因此需要一個類似DependencyResolutionLocatorKey的物件,用來做為由Locator取出PoolFactory的鍵值,這個物件必須覆載Equal、GetHashCode兩個函式,因為Locator會呼叫這兩個函式來比對鍵值,這個物件就是PoolLocatorKey。第二!PoolFactory在儲存體中沒有可使用物件時,會呼叫BuilderContext.HeadChain.BuildUp函式來建立該物件,這會引發重進入的問題,BuilderContext.HeadChain.BuildUp函式將會再次觸發PoolStrategy的BuildUp,而這裡又會再次呼叫BuilderContext.HeadChain.BuildUp,造成重入的問題,所以此處利用一個旗標:poolObjectCreating來解決這個問題。第三!PoolStrategy必須在TearDown函式被呼叫時,呼叫PoolFactory.ReleaseObject來將該物件歸還,此時會遭遇到一個問題,因為TearDown函式只會傳入物件實體,沒有id的資訊,這使得PoolStrategy無法於此處取得對應的PoolFactory物件,為了解決此問題,PoolStrategy宣告了一個_factoryMap物件,她是一個WeakRefDictionary<object, object>類別物件,在物件實體於BuildUp函式被建立後,PoolStrategy會將object/PoolFactory成對放入_factoryMap中,這樣就能於TearDown時以物件實體至_factoryMap中取出對應的PoolFactory物件了。


Testing



PoolStrategy的使用方式與SingletonStrategy類似,程式43是應用的程式碼列表。

程式43

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Practices.ObjectBuilder;


namespace OB_PoolStrategy

{


class
Program

{


static
void Main(string[] args)

{


Builder builder = new
Builder();

builder.Strategies.AddNew<PoolStrategy>(BuilderStage.PreCreation);



IPoolPolicy policy = new
PoolPolicy(true);

builder.Policies.Set<IPoolPolicy>(policy, typeof(TestObject), null);



Locator locator = new
Locator();


TestObject obj1 = builder.BuildUp<TestObject>(locator, null, null);


TestObject obj2 = builder.BuildUp<TestObject>(locator, null, null);

builder.TearDown<TestObject>(locator, obj1);

builder.TearDown<TestObject>(locator, obj2);


TestObject obj3 = builder.BuildUp<TestObject>(locator, null, null);


if (obj3 == obj1 obj3 == obj2)


Console.WriteLine("Pooled");


Console.ReadLine();


}

}



public
class
TestObject

{

}

}

圖9是執行結果。

圖9



八、ObjectBuilder 實務


8-1、ObjectBuilder With Configuration



截至目前為止,本文一直以程式方式來組態ObjectBuidler建立物件所需的各種物件,但在實務上,這些動作應該是交由組態檔來負責,這樣才能在不重新編譯應用程式的情況下,改變其行為或增加其功能。很幸運的,Microsoft於ObjectBuilder的範例中提供了一個途徑來達到此目的,該範例定義了一個.xsd檔案,其內定義了Constructor Injection、Setter Injection、Singleton、Type Mapping所需要的schema,當然!這個xsd中也定義了Strategy的schema,允許設計者透過組態檔來添加物件建立時所需使用的Strategys。


Consturctor Injection With Configuration



我將Microsoft所提供可組態ObjectBuidler的範例中關於處理組態檔時的.xsd及相關檔案提取出來,並添加Method Injection時所需要的schema及程式碼,放置於本文的範例程式檔中,當讀者們需要使用組態檔這個功能時,可以將Config目錄中的ObjectBuilderXmlConfig.xsd、ObjectBuilderXmlConfig.xsx、ObjectBuilderXmlConfig.cs、ObjectBuilderXmlConfig.Generate.cs等檔案複製到專案目錄中,並將ObjectBuilderXmlConfig.xsd、ObjectBuilderXmlConfig.Generate.cs加入到專案中,完成後再將ObjectBuilderXmlConfig.xsd設定成Embedded Resource,如圖10所示。

圖10


接著將修改ObjectBuilderXmlConfig.cs中關於由Resource中取得.xsd內容的程式碼,修正namespace為專案的預設namespace即可。

程式44

private
static
ObjectBuilderXmlConfig ParseXmlConfiguration(string config)

{

XmlSerializer ser = new
XmlSerializer(typeof (ObjectBuilderXmlConfig));

StringReader stringReader = new
StringReader(config);

XmlSchema schema =

XmlSchema.Read(

Assembly.GetExecutingAssembly().GetManifestResourceStream(

"OB_CSConfigurationTest.ObjectBuilderXmlConfig.xsd"), null);

XmlReaderSettings settings = new
XmlReaderSettings();

settings.ValidationType = ValidationType.Schema;

settings.Schemas.Add(schema);

XmlReader reader = XmlReader.Create(stringReader, settings);

ObjectBuilderXmlConfig configData = (ObjectBuilderXmlConfig) ser.Deserialize(reader);

return configData;

}

要使用組態檔來完成Constructor Injection,我們必須在專案中新增一個xml檔案,內容如下所示。

<object-builder-config
xmlns="pag-object-builder">

<build-rules>

<build-rule

type="OB_ConfigurationTest.InputAccept, OB_CSConfigurationTest"
mode="Instance">

<constructor-params>

<ref-param

type="OB_ConfigurationTest.PromptDataProcessor, OB_CSConfigurationTest"/>

</constructor-params>

</build-rule>

</build-rules>

</object-builder-config>

在ObjectBuilderXmlConfig.xsd定義中,build-rules代表著此BuilderContext中所有的物件建立規則,每個build-rule對應著一個『型別/id』,型別格式為<type,Assembly>,id部份則可透過添加name這個attribute來設定,未指定時就以null為預設值,如下。

<build-rule

type="OB_ConfigurationTest.InputAccept, OB_CSConfigurationTest"

name ="id1" mode="Instance">

每個build-rule可以擁有一個constructor-params區段,設計者可以在這個區段中添加value-param或是ref-param定義,前者是直接設定該參數的值,後者是透過reference方式來設值,本例中是將InputAccept建構子的第一個參數值指定為PromptDataProcessor。程式45是使用這個組態檔的程式列表。

程式45

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Configuration;

using Microsoft.Practices.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;


namespace OB_ConfigurationTest

{


class
Program

{


static
void Main(string[] args)

{


using (FileStream fs = new
FileStream("XmlFile1.xml", FileMode.Open, FileAccess.Read))

{


using (StreamReader sr = new
StreamReader(fs))

{


Builder builder = new
Builder(ObjectBuilderXmlConfig.FromXml(sr.ReadToEnd()));


InputAccept accept = builder.BuildUp<InputAccept>(new
Locator(), null, null);

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([Dependency(Name = "dataProcessor")]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

}

}


Setter Injection With Configuration



同樣的,我們也可以透過組態檔來完成Setter Injection,此範例所使用的組態檔如下。

<object-builder-config
xmlns="pag-object-builder">

<build-rules>

<build-rule

type="OB_CSPropertyInjectionTest.InputAccept, OB_CSPropertyInjectionTest"
mode="Instance">

<property
name="DataProcessor">

<ref-param


type="OB_CSPropertyInjectionTest.PromptDataProcessor, OB_CSPropertyInjectionTest"/>

</property>

</build-rule>

</build-rules>

</object-builder-config>

設計者必須將要設定的屬性定義放置於build-rule區段中的property,property有一個name attribute,代表著欲設定屬性的名稱,如要設定一個以上的屬性,只需添加多個property區段即可,程式46是使用此組態檔的程式碼。

程式46

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Configuration;

using Microsoft.Practices.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;


namespace OB_CSPropertyInjectionTest

{


class
Program

{


static
void Main(string[] args)

{


using (FileStream fs = new
FileStream("XmlFile1.xml", FileMode.Open, FileAccess.Read))

{


using (StreamReader sr = new
StreamReader(fs))

{


Builder builder = new
Builder(ObjectBuilderXmlConfig.FromXml(sr.ReadToEnd()));


InputAccept accept = builder.BuildUp<InputAccept>(new
Locator(), null, null);

accept.Execute();


Console.Read();

}

}

}

}



public
class
InputAccept

{


private
IDataProcessor _dataProcessor;


[Dependency(Name="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);

}

}





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

}

}


Method Injection With Configuration



Microsoft所提供的組態檔機制並未定義Method Injection的功能,我將其稍微修改來支援此功能,下面是Method Injection時所用的組態檔。

<object-builder-config
xmlns="pag-object-builder">

<build-rules>

<build-rule

type="OB_CSMethodInjectionTest.InputAccept, OB_CSMethodInjectionTest"
mode="Instance">

<method
name="SetDataProcessor">

<ref-param

type="OB_CSMethodInjectionTest.PromptDataProcessor, OB_CSMethodInjectionTest"/>

</method>

</build-rule>

</build-rules>

</object-builder-config>

用法與property大致相同,程式47是程式碼列表。

程式47

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using Microsoft.Practices.ObjectBuilder;


namespace OB_CSMethodInjectionTest

{


class
Program

{


static
void Main(string[] args)

{


using (FileStream fs = new
FileStream("XmlFile1.xml", FileMode.Open, FileAccess.Read))

{


using (StreamReader sr = new
StreamReader(fs))

{


Builder builder = new
Builder(ObjectBuilderXmlConfig.FromXml(sr.ReadToEnd()));


InputAccept accept = builder.BuildUp<InputAccept>(new
Locator(), null, null);

accept.Execute();


Console.Read();

}

}

}

}



public
class
InputAccept

{


private
IDataProcessor _dataProcessor;



public
void SetDataProcessor([Dependency(Name="dataProcessor")]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

}

}


Singleton With Configuration



透過組態檔,也可以定義那個『型別/id』的物件是使用Singleton模式,如下所示。

<build-rule
type="OB_CSMethodInjectionTest.PromptDataProcessor, OB_CSMethodInjectionTest"
mode="Singleton"/>


Type Mapping With Configuration



我們也可以在組態檔中定義Type Mapping,如下所示。

<build-rule

type="OB_CSMethodInjectionTest.IDataProcessor, OB_CSMethodInjectionTest"
mode="Instance">

<mapped-type

type="OB_CSMethodInjectionTest.PromptDataProcessor, OB_CSMethodInjectionTest"/>

</build-rule>


Customize Strategys



透過組態檔,也可以定義建立物件時所需要的Strategys,如下所示。

<object-builder-config
xmlns="pag-object-builder">

<strategies
include-default="false">

<strategy

type="Microsoft.Practices.ObjectBuilder.TypeMappingStrategy, Microsoft.Practices.ObjectBuilder"/>

<strategy

type="Microsoft.Practices.ObjectBuilder.CreationStrategy, Microsoft.Practices.ObjectBuilder"/>

<strategy

type="Microsoft.Practices.ObjectBuilder.MethodExecutionStrategy, Microsoft.Practices.ObjectBuilder"/>

.......................

</object-builder-config>

include-default屬性決定是否包含Builder物件所內建的Strategys。


九、後記



ObjectBuilder是一個相當不錯的Dependency Injection實作體,雖然相對於Spring、Avalon,ObjectBuilder並不是相當的完整,例如沒有完整的組態檔驅動特色,但她所具備的高度延展性,可以讓我們輕易的達到這個需求,且日後相信ObjectBuilder也會內建這些功能的。

1 comment:

Anonymous said...

This is great info to know.