一、前言
在我看來,OB2是一個用來構建和管理對象的開放性的框架,我們可以根據自己的需求去 擴展它,例如擴展它可以實現依賴注入(如MS的Unity)。我認為OB2最大的亮點之一是在提供 了對象創建框架的同時能夠管理對象以及對象之間的依賴關系,控制對象構建和銷毀過程, 這樣對象的創建就不是直接去使用new而對象的銷毀也不僅僅只靠GC了。要說OB2所使用的設 計模式,我覺得可以認為是大量使用策略(Strategy)模式並輔以責任鏈模式,通過責任鏈組 織對象創建或銷毀的次序及步驟。
二、關鍵組成部分
1、Locator
利用一個Dictionary對創建的對象建立<鍵,值>對,之後通過key可以找到相應的 對象,需要注意的是,這裡的值是通過System.WeakReference包裝過的短弱引用對象,之所 以這樣做是想在出現內存壓力或其他情況時允許Locator中的對象被GC回收,而強調使用短弱 引用是保證出現內存壓力時,對象最晚會在進行第一次finalize後被回收,注意GC和 finalize使用兩個不同的工作線程,GC線程被調用會觸發finalize線程開始工作,但GC線程 完成收集工作並不意味著finalize線程工作結束,這就意味著對象在GC線程開始工作到 finalize線程釋放目標對象之前,目標對象都可以被激活,而這裡不用強弱引用恐怕是因為 強弱引用對象會在第一次finalize和第二次finalize之間仍然存在,如果這時候目標對象被 激活可能導致對象狀態錯誤(因為也許對象中某些部分已經被回收)。
2、LifetimeContainer
以一個只讀的List<Object>結構存儲對象,管理對象的生命周期。不管是值類型還 是引用類型,都可以被加入到容器中,不管是同一個類型的同一個對象還是同一個類型的不 同對象都可以被同時加入容器,可以對容器中所有實現IDisposable接口的對象進行統一的釋 放。
3、Strategy與Policy
Strategy,是對算法的封裝,OB2中大量使用了策略模式,我們可以將對象的創建、銷毀 過程抽象成算法,利用Strategy進行封裝從而實現復用和倒置通用算法與具體實現之間的依 賴關系。
Policy,通過傳遞某些信息輔助Strategy,所謂傳遞的信息是多種多樣的,只要Strategy 需要就可以通過Policy傳入,因此有相當大的靈活性,當然Strategy也可以不使用Policy, 這就意味著該Strategy不需要通過外部傳遞給它信息就能實現算法。
Strategy與Policy相輔相成,為我們創建對象提供了很大的靈活度。
4、BuilderContext
構建上下文,我們的Strategy是以流水線的方式執行的,那麼對上下文環境進行封裝,為 其提供一個統一的狀態存儲和共享的環境是有必要的,這樣每個Strategy所針對的上下文環 境是確定的,可以清晰的看到這些變化,也就可以隨時截取這些變化,我們總是習慣將變化 的東西封裝起來,當然不同的上下文之間又可以起到相互隔離的作用,避免有意或無意的干 擾,讓對象只知道它應該知道的信息。最後,在構建上下文中存儲了最終生成的對象。
三、利用動態方法進行構造器注入
1、基本思路
確定待構建類型TypeToBuild;
為TypeToBuild建立一個動態方法(Dynamic Method),該動態方法的方法體的構建利用反 射發出(Reflection Emit)實現(使用MSIL),大致過程是:找到TypeToBuild的構造函數(優先 查找打了構造器注入標志的構造函數),利用特定Strategy+Policy對其參數進行解析並將解 析結果壓入Evaluation Stack,最後調用相應指令生成對象;
大家可以看到,這裡出現了一個新的變化點,所以需要建立一個與構造器注入相關的上下 文環境,這個環境封裝如下內容:待構建類型TypeToBuild、為生成TypeToBuild所需要的動 態方法(System. DynamicMethod)、用來在內存中生成MSIL代碼的ILGenerator對象、局部變 量,如圖:
由於構建過程中在不同策略間負責傳遞信息的是IBuilderContext(構建上下文),因此, 自然而然的在構建我們的動態方法時需要的信息也是從IBuilderContext中獲取,所以我們要 構建的用於生成TypeToBuild類型對象的方法原型類似於這樣:
Void BuildUp_*** (IBuilderContext context)
最終,生成的BuildUp_***方法被調用後即可生成相應對 象。
2、相關Strategy和Policy分析
Strategy:
① BuildPlanStrategy,用於構建目標類型的對象
它會在當前上下文中查找是否有構建 對象的計劃(繼承了IBuildPlanPolicy接口的Policy),如果沒有則在當前上下文中獲取繼承 了IBuildPlanCreatorPolicy類型的Policy來生成一個構建計劃,最後調用構建計劃的 BuildUp方法生成對象。如圖所示:
② DynamicMethodConstructorStrategy,用於構建與構造器注入相關的上下文環境,包括動態 方法的方法體,這裡給出構建動態方法的MSIL偽碼。
MSIL偽碼
.method public hidebysig virtual instance void BuildUp_"methodname" (IBuilderContext context) cil managed
{
.maxstack 3
.locals init(
[0] class typeToBuild 'existingObjectLocal'
[1] class [mscorlib]System.String 'currentParameterName')
L_0000: ldarg.0
L_0001: callvirt 'Existing'
L_0002: cast class typeToBuild
L_0003: stloc.0 //完成獲取方法所在類型的Type操 作並存在局部變量existingObjectLocal中,
//Evaluation Stack為空
L_0004: ldloc.0 //將 要構建的Type加載到局部變量區中
L_001f: ldnull
L_0005: ceq
L_0006: brfalse existingObjectNotNull //待構建類型為null 則返回
L_0007: ldarg.0
L_0008: call 'ThrowForAttemptingToConstructInterface' //判斷當前類型是否為接口
//是,則拋出異常
L_0009: BeginExceptionBlock
L_002e: nop
L_002f: ldstr 'parameters[i].Name' //獲取當前參數名稱並存儲於局部變量
//currentParameterName中
L_0010: stloc.1
L_0012: ldarg.0 //將上下文實例壓入Evaluation Stack
L_0013: callvirt 'Policies' //從上下文獲取PolicyList
L_0014: ldtoken 'typeof (IDependencyResolverPolicy)'//從MetaData
// 獲取IDependencyResolverPolicy
//類型的 token
L_0015: call System.Type.'GetTypeFromHandle' //執行call指令 ,依據句柄獲取類型,
//返回值為類型,為什麼要用這種方式?因為Policy 在存的時候以<IDependencyResolverPolicy,Key>
//方式存儲,所以 需要去metaData中找到IDependencyResolverPolicy的Type
L_0016: ldstr 'key with GUID' //獲取當前參數的BuildKey用來取出相應的Policy
L_0017: callvirt 'IPolicyList.Get' //調用其Get方法取出相應Policy
L_0018: cast class 'typeof(IDependencyResolverPolicy)'//完成Policy向
//IDependencyResolverPolicy的類型轉換
L_0019: ldarg.0 //向 Evaluation Stack中壓入上下文
L_0020: callvirt 'Resolve' //調用找 出的DependencyResolverPolicy的Resolve方法
//以生成參數並將參數壓入Evaluation Stack
L_0021: brtrue L_002e //構造函數的所有參數都解析完畢則跳出
L_0022: ldnull
L_0023: stloc.1 //清空局部變量 currentParameterName先
L_0024: newobj 'selectedCtor.Constructor' //其實就是將Evaluation Stack中參數先彈出,
//然後依據 ConstructorInfo信息調用待構建
//類型的構造函數,並將生成的對象壓入Evaluation Stack
L_0025: stloc.0 //將生成的對象保存到局部 變量區中,保證Evaluation Stack為空
L_0026: BeginCatchBlock //傳入 類型為Exception
exceptionOccuredInResolution:
L_0027: ldloc.1
L_0028: ldnull
L_0029: ceq
L_0030: brfalse exceptionOccuredInResolution
L_0031: rethrow //處理完異常之後再將其拋出
L_0032: ldloc.1
L_0033: ldstr 'signatureString'
L_0034: ldarg.0
L_0035: call 'throwForResolutionFailed'
L_0036: EndExceptionBlock
existingObjectNotNull:
}
構建流 程類似如下:
Policy:
① DefaultDynamicBuilderMethodCreatorPolicy,用來生成動態方法原 型:
Void BuildUp_*** (IBuilderContext context)
② ConstructorSelectorPolicy,輔助DynamicMethodConstructorStrategy,用於查找類型中打 了構造器注入標記的構造函數,並將其參數進行解析,對不同的參數如何解析是需要我們自 己擴展的。
③DynamicMethodBuildPlanCreatorPolicy,用來生成構建計劃,利用 DefaultDynamicBuilderMethodCreatorPolicy和DynamicMethodConstructorStrategy構建 BuildUp_***的動態方法並將結果保存於動態方法的上下文環境:
DynamicBuildPlanGenerationContext,最後利用動態方法的上下文環境生成該方法 的委托並作為參數生成構建計劃。
3、實例分析
需求:模擬建立一個線程池 ThreadPool,建立對象時需要傳入線程池的名字和允許最大線程數,使用OB2實現對象構建。
1、ThreadPool的原型如下,其構造函數有兩個參數,分別為String類型和Int32類型 :
ThreadPool
public class ThreadPool
{
private String poolName = String.Empty;
private Int32 maxThread = 10;
//[InjectionThreadPoll]
public ThreadPool()
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("The default constructor is called...");
}
[InjectionThreadPoll]
public ThreadPool(String name, Int32 num)
{
this.poolName = name;
this.maxThread = num;
OutPut();
}
private void OutPut()
{
this.SetForegroundColor(ConsoleColor.White);
Console.Write("The thread pool's name is ");
this.SetForegroundColor(ConsoleColor.Red);
Console.WriteLine(this.poolName);
this.SetForegroundColor(ConsoleColor.White);
Console.Write("The number of threads in a thread pool should not exceed ");
this.SetForegroundColor(ConsoleColor.Red);
Console.WriteLine(this.maxThread);
}
private void SetForegroundColor(ConsoleColor color)
{
Console.ForegroundColor = color;
}
2、需要使用的Strategy和Policy如下:
① OB2自定義的
BuildPlanStrategy、DynamicMethodConstructorStrategy、 DefaultDynamicBuilderMethodCreatorPolicy、ConstructorSelectorPolicy、 DynamicMethodBuildPlanCreatorPolicy
② 需要自己擴展的
這部分主要集中到參數的解析上,我的做法是建立一個ParametersResolveStrategy,專 門用來解析參數,此時傳來的上下文中包含參數的類型,利用參數類型去尋找相應的Policy ,ParametersResolveStrategy+Policy可以得到參數實例。
參數通過配置文件讀出,Policy的實現如下:
ParameterChooserPolicy
public interface IParameterChooserPolicy : IBuilderPolicy
{
Object GetValue();
}
public class ParameterChooserPolicy<TParameterType> : IParameterChooserPolicy
{
public virtual Object GetValue()
{
Object obj = ConfigurationManager.AppSettings [GetNameOfType(typeof(TParameterType))];
return Convert.ChangeType(obj,typeof (TParameterType));
}
private String GetNameOfType(Type type)
{
return type.ToString();
}
}
3、建立一個ThreadPoolBuilder類,用來配置Strategy和Policy,實現如下:
ThreadPoolBuilder
public class ThreadPoolBuilder<TTypeToBuild>
where TTypeToBuild : class
{
private NamedTypeBuildKey key;
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();
private IReadWriteLocator locator = null;
private ILifetimeContainer lifetime = null;
public ThreadPoolBuilder()
{
key = new NamedTypeBuildKey(typeof (TTypeToBuild));
this.strategies.Add(new BuildPlanStrategy(), BuilderStage.PreCreation);
this.strategies.Add(new ParametersResolveStrategy(), BuilderStage.Creation);
this.SetInnerStrategies();
}
public IReadWriteLocator Locator
{
get
{
return this.locator;
}
set
{
this.locator = value;
}
}
public ILifetimeContainer Liftime
{
get
{
return this.lifetime;
}
set
{
this.lifetime = value;
}
}
public virtual IStagedStrategyChain SetInnerStrategies()
{
StagedStrategyChain<BuilderStage> innerStrategies = new StagedStrategyChain<BuilderStage>();
innerStrategies.Add(new DynamicMethodConstructorStrategy(), BuilderStage.PreCreation);
return innerStrategies;
}
public virtual IPolicyList GetPolicies()
{
PolicyList policies = new PolicyList();
policies.Set<IBuildPlanCreatorPolicy>(new DynamicMethodBuildPlanCreatorPolicy(SetInnerStrategies()), key);
policies.Set<IDynamicBuilderMethodCreatorPolicy>(new DefaultDynamicBuilderMethodCreatorPolicy(), key);
policies.Set<IConstructorSelectorPolicy>(new ConstructorSelectorPolicy<InjectionThreadPollAttribute>(), key);
policies.Set<IParameterChooserPolicy>(new ParameterChooserPolicy<String>(), typeof(String));
policies.Set<IParameterChooserPolicy>(new ParameterChooserPolicy<Int32>(), typeof(Int32));
return policies;
}
public TTypeToBuild BuildUp()
{
return new Builder().BuildUp<TTypeToBuild> (
this.locator,
this.lifetime,
this.GetPolicies(),
this.strategies.MakeStrategyChain(),
key,
null);
}
}
最後,使用的時候這樣就可以了:
ThreadPool t1 = new ThreadPoolBuilder<ThreadPool>().BuildUp();
4、總結
我覺得學習OB2,看懂每行代碼不如看懂它的思想,理解為什麼這麼設計才是關鍵。順便 說一句,據說OB2中動態方法這一部分是為了Unity而加的,在OB2中用動態方法分別實現了構 造器注入、屬性注入和方法注入,實現類似,基本上看懂一個就看懂全部了,希望大家多提 寶貴意見,看看怎麼樣才能把OB2用好。
本文源代碼: http://files.cnblogs.com/vivounicorn/Ob2DynamicMethodSolution.rar