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
』標示為
Singleto
n
,設計者必須於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);
}
|
P
S
:注意,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
再加入
TypeMappingStrateg
y
時,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
前面或後面,皆可正常運作。不過同一類型的
Strateg
y
,但有順序需求的情況下,仍然要小心調整順序,程式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
等之類,第二個參數是對應的型別,第三個參數則是
i
d
。設計者可以呼叫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
都必須直接或間接實作此介面,內建實作此介面的類別是
ReadableLocato
r
,她是一個抽象類別。真正完成實作可用的是ReadOnlyLocator,這個Locator只允許讀取,不允許新增。
6-2
、
ReadWrite Locator
ObjectBuilder
中支援讀與寫的
Locator
是
ReadWriterLocator
,與
ReadOnlyLocator
一樣,她也是一個抽象類別,真正完成實作的是
Locator
類別。附帶一提,雖然
Locator
定義了蠻清楚的階層,但是
BuilderContext
只支援實作
IReadWriterLocator
介面的
Locato
r
。
6-3
、
WeakRefDictionary and Locator
Locator
類別是我們一直都在使用的
Locator
,她是一個繼承自
ReadWriterLocator
的類別,值得一提的是,她使用一個
WeakRefDictionary
來儲存設計者所放入
Locator
的物件,
WeakRefDictionary
內部對於每個元素都會以
WeakReference
封裝,這意味著,
Locator
中的元素並無法保證一直都存在,因為
CLR
會在記憶體拮据時,先行釋放
WeakRefernce
所封裝的物件,這點讀者必須謹記。
七、
Extending ObjectBuilder
ObjectBuilder
除了支援三種
Dependency Injection
模式、
Service Locator
之外,最大的魅力應該來自於具高度延展性的架構,設計者可以透過撰寫
Strateg
y
、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 Injectio
n
。
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
介面,該介面必須直接或間接繼承自
IBuilderPolic
y
介面,這是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
模式,意味所有的物件建立動作都會獲得一個新的物件實體,跟
ne
w
、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
中取出
PoolFactor
y
,如果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
