Inside ObjectBuilder Part 3

摘要:Inside ObjectBuilder Part 3

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

 

五、Misc
 
5-1SingletonStrategy
 
  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中,那麼SingletonStrategyBuildUp函式將直接傳回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-2TypeMappingStrategy
 
 前面的章節早已使用過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)
TypeMappingStrategyBuildUp函式被呼叫時,她會以『型別/id』取得對應的ITypeMappingPolicy物件,透過她來取得對應的型別,之後將使用這個型別呼叫下一個StrategyBuildUp函式,這就是Type Mapping的流程。
PS:注意,Type Mapping型別必須相容,如介面->實作、基礎類別->衍生類別。
 
5-3BuildAwareStrategy
 
 BuildAwareStrategy可以於實作IBuilderAware介面物件建立或釋放時,呼叫對應的OnBuildUpOnTearDown函式,如程式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-4BuildUp的第三、四個參數
 
 截至目前為止,我們的例子在呼叫BuildUp函式時,第三及四個參數都傳入null,這兩個參數的用途究竟為何呢?這要先從BuildUp函式的宣告談起。
object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild);
當我們於呼叫BuildUp函式指定existing為一物件實體時,CreationStrategy將不會建立任何新的物件,只會進行Singleton模式物件的相關動作,然後就呼叫下一個Strategy物件的BuildUp函式,簡單的說!在CreationStrategy後的Strategy仍然會運行,例如Method InjectionSetter 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物件,分別以ID2ID2id加到了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』來建立兩個同型別但不同idSingleton物件,如程式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-5StrategyList
 
  在本文一開始的範例中,我們使用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會依據PreCreationCreationInitializationPostInitialization的順序來產生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-6PolicyList
 
 BuilderContext所定義的Policies物件型別為PolicyListPolicyList物件以Dictionary<BuilderPolicyKey, IBuilderPolicy>物件來儲存設計者所加入的Policy物件,其中用來作為鍵值的BuilderPolicyKey類別建構子如下。
public BuilderPolicyKey(Type policyType, Type typePolicyAppliesTo, string idPolicyAppliesTo)
第一個參數為policyType,也就是ICrationPolicyITypeMappingPolicy等之類,第二個參數是對應的型別,第三個參數則是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 2005Class Diagram來觀察ObjectBuilder中的Locator階層架構。
7
6-1Readonly Locator
 
 ObjectBuidler定義了一個IReadableLocator介面,所有的Locator都必須直接或間接實作此介面,內建實作此介面的類別是ReadableLocator,她是一個抽象類別。真正完成實作可用的是ReadOnlyLocator,這個Locator只允許讀取,不允許新增。
 
6-2ReadWrite Locator
 
 ObjectBuilder中支援讀與寫的LocatorReadWriterLocator,與ReadOnlyLocator一樣,她也是一個抽象類別,真正完成實作的是Locator類別。附帶一提,雖然Locator定義了蠻清楚的階層,但是BuilderContext只支援實作IReadWriterLocator介面的Locator
 
6-3WeakRefDictionary 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-1EventSetterStrategy
 
 ObjectBuidler提供了Constructor InjectionInterface Injection(Method Ijection)Setter Injection(Property Injection)三種Injection模式,雖然ObjectBuilder只提供了Propety式的Setter Injection,不過我們可以藉助於ObjectBuilder高度的延展性架構,讓ObjectBuidler也能支援Event Injection
 
IEventSetterInfo
 
 Event InjectionProperty Injection同屬Setter Injection模式,兩者運作的模式也極為相似,ObjectBuilderProperty Injection部份是由ProperySeterInfoPropertySetterPolicyPropertySetterStrategy三個類別所構築而成,我們可以依循這個既定架構,實作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-2PoolStrategy
 
   GoF的書中,提出了三種物件管理Pattern,一是Singleton,意味著物件一旦建立後,就存放於某個儲存體中,之後所有要求物件的建立動作,都將會獲得同樣的物件實體,在ObjectBuilder中實現這個Pattern的就是SingletonStrategy。第二個PatternSingleCall模式,意味所有的物件建立動作都會獲得一個新的物件實體,跟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