摘要:Inside ObjectBuilder Part 3
Object Builder Application Block
 文/黃忠成    
 2006/9/21
 
五、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
 