Inside ObjectBuilder Part 2

    技术2022-05-11  26

     

    Object Builder Application Block   / 黃忠成     2006/9/21 

     

    三、 ObjectBuilder Application Block     ObjectBuilde r 一開始 出現於 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 l           允許要求一個抽象物件或介面,ObjectBuilder會依據程式或組態檔的設定,傳回一個實體物件。 l           回傳一個既存物件,或是每次回傳一個新的物件(多半用於Dependency、Singleton情況,稍後會有詳細說明)。 l           透過特定的Factory建立一個物件,這個Factory可以依據組態檔的設定來建立物件(CustomFactory,隸屬於Enterprise Common Library)。 l           當物件擁有一個以上的建構子時,依據已有的參數,自動選取相容的建構子來建立要求的物件。(Consturctor Injection) l           允許物件於建立後,透過程式或組態檔來賦值至屬性,或是呼叫特定的函式。(Setter Injection、Interface Injection) l           提供一組Attribute,讓設計師可以指定需要Injection的屬性,亦或是於物件建立後需要呼叫的函式,也就是使用Reflection來自動完成Injection動作。 l           提供IBuilerAware介面,實作此介面的物件,ObjectBuilder會於建立該物件後,呼叫OnBuildUp或是OnTearDown函式。 l           提供TearDown機制,按建立物件的流程,反向釋放物件。 對於多數讀者來說,這些官方說明相當的隱誨,本文嘗試由架構角度切入,討論 ObjectBuidler 的主要核心概念,再透過實作讓讀者們了解,該如何使用 ObjectBuidler   3-1 The Architecture of Object Builder   圖2 2 ObjectBuilder 中四個主要核心物件的示意圖, BuidlerContext 是一個概念型的環境物件,在這個物件中,包含著一組 Strategys 物件,一組 Polices 物件,一個 Locato r 物件, 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 Injectio n 。而MethodExecutionStrategy則會與MethodReflectionStrategy合作,在物件建立後,呼叫特定的函式,也就是Method Injection(視使用方式,Interface Injection是以此種方式完成的)。   Post-Initialization Strategy     在物件建立並完成初始化動作後,就進入了 Post-Initialization Strateg y 階段,在此階段中,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 strategies = new StrategyList ();   public BuilderBase() { }   public PolicyList Policies {            get { return policies; } }   public StrategyList 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 ( 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     最後一個尚未釐清的關鍵元素是 Locato r ,我們於呼叫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: ObjectBuidler Locator 離完善的 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 並未要求建構者傳入任何物件實體,而是要求建構者傳入注入時對應的參數型別、參數名稱、實體型別、 NotPersentBehavoi r 及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 時所傳入的第三個參數相同。最後一個問題是,如果每個『型別/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 來建立物件,不同的是其不會先搜尋 Locato r ,也無法作參數型別與實體型別對應,因此無法適用於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 Injectio n 時,設計者必須在建立物件前,預先準備這些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 ,否則建立一個新的 Dependenc yParameter ,她會單以型別參數來建立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 Attribut e 、或是使用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 Attribut e 或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 有兩個特色,一是其會選擇頂端的建構子,二是其一律以 BuidU p 函式依據參數型別來建立參數物件,不需要設計者介入。那在何種情況下選擇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 來完成,指的是在物件建立後,呼叫所指定的函式來完成初始化動作,而負責這個工作的就是 MethodExecutionStrateg y ,本節持續延應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 物件。在 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 Injectio n ,用法與前述的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 Wor k     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 MethodReflectionStrateg y ,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 Injectio n ,如程式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;        }     } ............... }   v 

    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1282150


    最新回复(0)