摘要: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