自然而然的Object Builder 应用过程(一)
兄弟,你可能从来没听说过Object Builder,它很老了,但却是CAB构建对象的核心,而CAB框架协助开发人员在桌面程序中使用MVC,提高开发效率。CAB最大限度缓解了NET程序员的痛苦。为了程序员忍受的痛苦,为了程设的无奈,为了无尽的调试和混乱的逻辑,真心-------希望你能看这整篇枯涩文档。
Object Builder是个简单的框架,但赋予我们构建复杂对象实例的能力,若专业地讲Object Builder是“通过策略和配置信息自动创建对象实例的对象构造器”。在很多场合下Object Builder被认为是IOC模式的微软实现。有人认为它太“轻”,实际上正是因为这种“轻”才为开发者提供了强大的扩展性。人们在设计中常有这样的认识,“重”的框架要比“轻”的框架更稳妥,功能更强,会给开发者提供更多的方便,能更迅捷的完成开发。但在实际工程中,我们发现使用不当的“重”框架也能给我们带来更“重”的麻烦。同是“学院派”的设计,人们既然能容忍Enterprise Library之“重”,可为什么不能接受Object Builder之“轻”呢。
让我们开始理解Object Builder之旅吧。
对象,对象,都是对象。在NET程序中对象无处不在。我们的程序实际上就是引入类、创建对象实例、使用对象实例、销毁对象实例的过程。对象大到From UI 小到一个Point或Size,我们无时无刻不在呵护它们,稍不留意,错误就接踵而至。在某些极端情况下,当我们所定义的业务逻辑复杂到一定程度-----对象嵌着对象,数据套着数据,而我们的设计还要考虑灵活性和松耦合时,梦魇将由此开始,而创建这些复杂的对象实例又是噩梦中的噩梦。正是鉴于此GOF23定义了Builder设计模式,也称为创建者模式。通过使用创建者模式,我们能精细而巧妙地构建对象的各部分,最后在黑箱中组装完成,将具有完整功能的对象实例提供给使用者。Microsoft通过Object Builder实现了Builder模式,为我们构建复杂对象提供了便捷的工具,也让我们得以一窥框架设计大师们的风范。
要谈Object Builder就不得不了解Microsoft的模式与实践小组(P&P)。Object Builder最早出现在P&P 小组的Composite UI Application Block (CAB)中,通过创建者设计模式和责任链模式构建WorkItem对象。Object Builder诞生不久,即被分离出来,引入Enterprise Library2006中,成为创建复杂对象实例的标准实现。至此Object Builder就存在于P&P的标准体系中,并不断的进化发展,日臻完善。
同P&P小组的CAB、SCSF、WCSF以及Enterprise Library一样,Object Builder也带有浓郁的“学院”风格,它更像是IOC在学术领域的实现,而非工程领域实现。其实我更倾向于认为,P&P设计Object Builder的目的是为了与其它IOC框架相抗衡。在2005--2006年正是JAVA的Spring呼风唤雨之时,P&P小组也设计了Object Builder。在P&P的设计中也充分的体现了Microsoft “永远借鉴别人而成功” 的一贯风格。
在业务逻辑的框架设计中,我们将Object Builder定义为创建对象实例的核心工具,嵌入到到框架中,通过它分步骤、分阶段地建立“大”的业务逻辑对象实例。我们要得到业务逻辑对象时,通过调用Object Builder的BuildUp获得对象实例。当业务逻辑对象使用完成后,我们通过TearDown来析构对象实例。在Object Builder创建和析构对象的过程中,运用控制Strategy和Polices的手段,来控制、调整对象实例创建、析构流程,并使用一系列的Attribute实现初始化参数的依赖注入。至于 BuildUp返回的对象实例到底是什么、如何构建之类的问题,早已封闭于黑箱之内。我们唯一要做得可能就是用GetType().Isxxxxxx进行些类型检查罢了。
Object Builder不但具有与其它Microsoft的作品一样容易上手、易用的特点,也继承了P&P的传统——为广大使用者提供了完整的源代码和测试脚本,我认为,正是由于这些原因Object Builder不但是一个精巧的框架,也是学习设计模式的典范。
平心而论P&P在设计Object Builder框架时,侧重于如何“精细”的构建/装配实例。这就决定了它适合于构建复杂对象。若要构建Point或Size之类的简单实例是万万用不到Object Builder的。再多说一句在Enterprise Library4.1中Object Builder 2/Unity已经被引入,不过其实质依然是责任链方式。
Object Builder的世界观
从全局角度来看Object Builder就是一个可控的new ,一个应用了责任链模式的创建者模式实现。下图在应用层面表述了构成Object Builder的对象及其关系。
该示意图虽没有将Object builder中的对象继承关系表现出来,但基本的类都已得到了展现,虽然纵深不够,但还是表现了类的横向关系。下面就用通过一个简单的helloWord例子来观察Object builder创建对象实例的具体步骤。
首先,我们先来定义一个要通过Object builder创建的对象TestObject。
namespace TestApp
{
public class TestObject
{
public void ShowHello()
{
Console.WriteLine("hello Word");
}
}
}
这个类非常简单,但足以说明问题。在以后的篇幅中,我们还会通过不断的完善TestObject类来展示Object builder是如何将参数注入到创建的对象实例中的。
TestObject的功能及其简单,通过定义方法ShowHello()在控制台上显示问候串。需要提醒注意的是TestObject未明确的定义构造函数,所以它具备一个默认的构造函数,这一点在以下的例子中非常重要。
建立主程序
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace TestApp
{
class TestApp
{
static void
{
Builder Testbuilder = new Builder();
TestObject obj = Testbuilder.BuildUp<TestObject>(new Locator(), null, null);
obj.SayHello();
Console.ReadLine();
}
}
}
这里 Microsoft.Practices.ObjectBuilder是Object builder的命名空间,在应用中通过引入Microsoft.Practices.ObjectBuilder.dll获得。我们实例化了Builder类,并通过调用它的BuildUp<TestObject>获得TestObject实例。
其实无论我们要实例化的类有多么复杂,在Object builder中创建步骤都是相同的。
- 实例化了Builder类。
- 针对要创建的类初始化设定Builder。
- 通过TestObject obj = Testbuilder.BuildUp<TestObject>(new Locator(), null, null)形式获得创建完成的对象实例。
下面就让我同大家一起入Object builder的世界看看对象实例是如何被创建出来的。
在我们建立的TestApp程序中,最突出的对象是Builde类型的Testbuilder,它是实例的“出站口”。在Object builder中Builde负责引导我们创建对象,程序中使用Object builder时,一般都要建立Builder类实例。Builder是BuilderBase派的生类,Builder通过对BuilderBase的扩展,在内部建立起默认Strategy列表,并调用一系列Strategy类的BuilderUp函数形成责任链创建对象实例。我们现在就把Builde拆开,来看看Microsoft的大师们是如何实现这个对象的。
Builde的继承关系如图
Builde是一个实现了IBuilder接口的BuilderBase的派生类。接口定义IBuilder如下
public interface IBuilder<TStageEnum>
{
PolicyList Policies { get; }//返回Policy的列表。
StrategyList<TStageEnum> Strategies { get; }//返回Strategy的列表。
//调用责任链创建类
object BuildUp(IReadWriteLocator locator, Type typeToBuild, string idToBuild, object existing,
params PolicyList[] transientPolicies);
//调用责任链创建类的泛型方法,我们往往使用这个函数调用来获得对象。
TTypeToBuild BuildUp<TTypeToBuild>(IReadWriteLocator locator, string idToBuild, object existing,params PolicyList[] transientPolicies);
//反向调用责任链析构对象
TItem TearDown<TItem>(IReadWriteLocator locator, TItem item);
}
基类BuilderBase实现了大部分Object builder创建对象实例的功能。在设计中经常从BuilderBase中派生自定义类,来获得更简约的Builder。
我们应注意BuilderBase的一个重载的构造函数。函数说明如下
public BuilderBase(IBuilderConfigurator<TStageEnum> configurator)
{
configurator.ApplyConfiguration(this);
}
该构造函数引入了实现IBuilderConfigurator接口的configurator参数。IBuilderConfigurator接口必须由开发者自己实现,引入configurator的动机是为开发者提供一种在外部配置BuilderBase的手段。IBuilderConfigurator接口只包含一个函数ApplyConfiguration(IBuilder<TStageEnum> builder)。参数builder是实现IBuilder<TStageEnum>接口的类,在实际中就是BuilderBase的实现类,所以我们可以把实现了IBuilderConfigurator接口的实例,作为configurator参数,传入BuilderBase(IBuilderConfigurator<TStageEnum> configurator)构造函数,在外部实现对BuilderBase的控制。其代码如下
class TestBuilderConfigurator : IBuilderConfigurator<BuilderStage>
{
#region IBuilderConfigurator<TStageEnum> 成员
public void ApplyConfiguration(IBuilder<BuilderStage> builder)
{
builder.Strategies.AddNew<MyTestStrategy>(BuilderStage.Initialization);
builder.Strategies.AddNew<ObjectFindStrategy>(BuilderStage.Initialization);
builder.Policies.SetDefault<IMyTestPolicy>(new MyTestPolicy("hello my test"));
builder.Policies.SetDefault<ObjectFindPolicy>("gnbyns");
}
#endregion
}
在实例化BuilderBase时代码如下
Builder testBuilder = new Builder (new TestBuilderConfigurator());
通过以上IBuilderConfigurator实现类TestBuilderConfigurator,修改了testBuilder内部的Strategies与Policies,从而起到了在外部定制BuilderBase的目的。而Builder的缺省构造函数也是调用带有IBuilderConfigurator参数的构造函数来实现的,只不过传递的是null值。
public Builder()
: this(null){}
BuilderBase包含了Policies和Strategies属性,它们分别暴露PolicyList和StrategyList类型的私有字段。而PolicyList和StrategyList类,通过被它们包装的Dictionary记录了Builder中将被使用的Policy集合和Strategy集合。
通俗的讲,在Object builder中Strategy决定了 “在什么位置上通过那个Policy建立或初始化对象”。Policy决定了“在Strategy定义的某个点上如何建立或初始化对象”。所以BuilderBase中Policies和Strategies属性就包含了构建对象的全部信息,我们在实际工程中要“修剪”的就是Policies和Strategies两个属性。
BuilderBase中Strategies属性是StrategyList<TStageEnum>类型。TStageEnum实际上就是BuilderStage枚举。具体如下:
public enum BuilderStage
{
PreCreation,//准备创建阶段
Creation,//创建阶段
Initialization,//创建对象后进行初始化阶段
PostInitialization//创建最后完成阶段
}
BuilderStage的作用就是说明某个Strategy作用在那个创建的阶段,起到在BuilderBase中锚定Strategy的作用。这里还有必要再次明确两个类Strategy和Policy。打个比方,它们之间有点像螺杆和螺帽的关系,Strategy像“螺钉”被BuilderStage固定在BuilderBase构建对象的不同阶段,对将被构建的对象实例,Strategy只起到了“地标”的作用,只是标定了在构建对象过程中的一个个点,至于这些点到底要做什么,则是由拧在Strategy上的“螺帽”Policy决定的。若某个Strategy螺杆上没有螺帽Policy,就是一个“空白点”,这个点将被跳过,不会产生任何实质的动作,但有时也存在意外,在某个Strategy中完成了所有的工作,而不需要引入某个Policy。
Object builder正是通过BuilderStage实现对责任链设计模式中“链”的控制。StrategyList<TStageEnum>关系如图
public class StrategyList<TStageEnum>
{
private readonly static Array stageValues = Enum.GetValues(typeof(TStageEnum));
//获得创建阶段的常数枚举值
private Dictionary<TStageEnum, List<IBuilderStrategy>> stages;
private object lockObject = new object();
静态的只读Array类型stageValues参数,包含了在BuilderStage枚举中定义的所有值。其顺序在BuilderStage中已经定义完成,这就决定了我们的Strategy具有从PreCreation到PostInitialization的排列顺序。object lockObject只是一个标注,通过lock (lockObject)使StrategyLis具有线程内的安全性。
StrategyList的构造函数初始化了它内部的 Dictionary<TStageEnum, List<IBuilderStrategy>> stages;字段,stages是List<IBuilderStrategy>的一个容器。
public StrategyList()
{
stages = new Dictionary<TStageEnum, List<IBuilderStrategy>>();
foreach (TStageEnum stage in stageValues)
{
//建立一个以BuilderStage枚举值为键值的列表字典
stages[stage] = new List<IBuilderStrategy>();
}
}
当StrategyList构建完成后内部的stages结构如下
Key |
Value |
PreCreation |
List<IBuilderStrategy> |
Creation |
List<IBuilderStrategy> |
Initialization |
List<IBuilderStrategy> |
PostInitialization |
List<IBuilderStrategy> |
stages的Value中保存的是IBuilderStrategy接口对象的List。由于在List类型中成员的顺序由成员插入顺序决定,就产生了一个隐蔽的问题,即在每个BuilderStage阶段插入Strategy的顺序将决定了BuilderBase调用Strategy的顺序,这是我们在开发中应注意的问题。
public void Add(IBuilderStrategy strategy, TStageEnum stage)
{ lock (lockObject)
{stages[stage].Add(strategy);}
}
public void AddNew<TStrategy>(TStageEnum stage)where TStrategy : IBuilderStrategy, new()
{lock (lockObject)
{stages[stage].Add(new TStrategy());}
}
StrategyList中的Add方法将一个IBuilderStrategy的实现添加到BuilderStage的某个阶段中。因为被lock (lockObject)包装,所以Add方法具备了线程内安全。AddNew是个泛型方法,并通过where TStrategy : IBuilderStrategy, new()进行了约束。
public void Clear()
{lock (lockObject)
{foreach (TStageEnum stage in stageValues)
stages[stage].Clear();}}
StrategyList中的Clear()方法将清空stages,它同样具备线程内安全。
public IBuilderStrategyChain MakeStrategyChain()
{
lock (lockObject)
{
BuilderStrategyChain result = new BuilderStrategyChain();
foreach (TStageEnum stage in stageValues)
result.AddRange(stages[stage]);
return result;
}
}
StrategyList的MakeStrategyChain是较重要的方法,它返回了BuilderStrategy的正向链表。IBuilderStrategyChain及其实现BuilderStrategyChain具体如下
BuilderStrategyChain中strategies是个 List<IBuilderStrategy>,包含了IBuilderStrategy的所有实现。Head返回的是strategies中的strategies[0],就是第一个Strategy,是链表的头。AddRange(IEnumerable strategies)将实现了IEnumerable 接口strategies 拼接到strategies的结尾处。GetNext(IBuilderStrategy currentStrategy)通过遍历,返回链表中currentStrategy之后的下一个IBuilderStrategy实现。
StrategyList的MakeStrategyChain给我们提供了根据BuilderStage排序的List<IBuilderStrategy>,为责任链模式提供了链表。它同样是线程内安全。
public IBuilderStrategyChain MakeReverseStrategyChain()
{
lock (lockObject)
{
List<IBuilderStrategy> tempList = new List<IBuilderStrategy>();
foreach (TStageEnum stage in stageValues)
tempList.AddRange(stages[stage]);
tempList.Reverse();
BuilderStrategyChain result = new BuilderStrategyChain();
result.AddRange(tempList);
return result;
}
}
MakeReverseStrategyChain()用Reverse()提供了一个反向构建的链表,其目的是在析构对象实例时应用它实现Strategy的反向操作。
BuilderBase中Policies属性是PolicyList类型。PolicyList相对于StrategyList结构比较简单。具体如下
PolicyList的构造函数传入了可变数目参数,并调用AddPolicies将他们添加到类型为Dictionary<BuilderPolicyKey, IBuilderPolicy>的 policies中,AddPolicies同样是线程内安全。
public PolicyList(params PolicyList[] policiesToCopy)
{
if (policiesToCopy != null)
foreach (PolicyList policyList in policiesToCopy)
AddPolicies(policyList);
}
BuilderPolicyKey是一个由policyType、typePolicyAppliesTo、idPolicyAppliesTo构成的简单结构,而非一个类,所以BuilderPolicyKey是“传值”而非“传引用”,BuilderPolicyKey起到了一个复合Key的作用。在PolicyList中的核心方法往往都需要个BuilderPolicyKey键。
PolicyList 的属性Count是一个具备线程安全的只读属性,返回了PolicyList中包含Policy的个数。
PolicyList 的方法Clear、ClearAll、ClearDefault负责清除PolicyList中的Policy,他们同样具备线程安全性。
PolicyList 的一系列Get方法为在Strategy中获得某个Policy提供了手段,同样他们也具备线程安全性。
PolicyList 的一系列Set方法实现了向PolicyList中插入Policy。实际开发中PolicyList的Get方法和Set方法我们使用得非常频繁。
BuilderBase中Policies和strategies属性为创建对象准备了材料,而用材料创建实际对象实例,是由BuilderBase的核心方法BuildUp实现的。
BuildUp方法有两个实现版本,其中一个实现了泛型的应用,具体不需要过多的解释,只是通过强制类型转化获得的一个强的类型。方法声明如下:
public TTypeToBuild BuildUp<TTypeToBuild>(IReadWriteLocator locator,
string idToBuild,
object existing, params PolicyList[] transientPolicies)
{return (TTypeToBuild)BuildUp(locator, typeof(TTypeToBuild), idToBuild, existing, transientPolicies);}
而非泛型版本是实现Builder的关键,正是通过它,对象实例在责任链中被构建完成。
public virtual object BuildUp(IReadWriteLocator locator, Type typeToBuild,
string idToBuild, object existing, params PolicyList[] transientPolicies)
{
if (locator != null)
{
lock (GetLock(locator))
{
return DoBuildUp(locator, typeToBuild, idToBuild, existing, transientPolicies);
}
}
else
{
return DoBuildUp(locator, typeToBuild, idToBuild, existing, transientPolicies);
}
}
我们看到BuildUp 通过判断locator是否为空, locator存在将在lock的保护下调用帮助函数,若为空直接调用帮助函数。BuildUp方法具有了5个参数,参数说明见下表
参数名称 |
类型 |
参数的作用 |
构建方式 |
locator |
IReadWriteLocator |
在Object builder中Locator是一个相当重要的对象,起到了管家的作用,在以后会详细讨论。现在我们可以把它认为是一个缓存,用来暂时保存临时的数据,Object builder中主要用它来保存构造时的参数。 |
Locator类型是IReadWriteLocator的基本实现,我们往往通过使用它就足以完成任务。
|
typeToBuild |
Type |
我们要构建的对象实例的类型 |
typeof(TestObject) |
idToBuild |
String |
主导着ObjectBuilder的类型识别和对象识别,实现通过ID区分对象创建。 |
|
existing |
object |
已经存在的typeToBuild类型对象 |
|
transientPolicies |
PolicyList |
应用临时的可变数目参数Policy集合构建对象。 |
|
其实Object builder中的大部分功能都是围绕着这5个参数来实现的,这些参数就是创建对象实例的五个要素。BuildUp的帮助函数DoBuildUp如下
private object DoBuildUp(IReadWriteLocator locator, Type typeToBuild, string idToBuild, object existing,
PolicyList[] transientPolicies)
{
IBuilderStrategyChain chain = strategies.MakeStrategyChain();
ThrowIfNoStrategiesInChain(chain);
IBuilderContext context = MakeContext(chain, locator, transientPolicies);
//获得Object builder的跟踪信息配置
IBuilderTracePolicy trace = context.Policies.Get<IBuilderTracePolicy>(null, null);
if (trace != null)
trace.Trace(Properties.Resources.BuildUpStarting, typeToBuild, idToBuild ?? "(null)");
//开始调用链头
object result = chain.Head.BuildUp(context, typeToBuild, existing, idToBuild);
//调用跟踪
if (trace != null)
trace.Trace(Properties.Resources.BuildUpFinished, typeToBuild, idToBuild ?? "(null)");
return result;
}
DoBuildUp方法先通过MakeStrategyChain()获得正向的Strategy列表chain,ThrowIfNoStrategiesInChain实现了对chain的异常检查,MakeContext创建了上下文环境context,然后通过chain.Head的方法BuildUp(context, typeToBuild, existing, idToBuild)启动了责任链,返回了创建的对象实例。
BuilderBase中的TearDown<TItem>方法的帮助方法DoTearDown与DoBuildUp及其相似,只是通过MakeReverseStrategyChain()获得了反向的列表,然后调用chain.Head.TearDown(context, item)析构对象。
在DoBuildUp方法以及DoTearDown中,我们会注意到它们都通过MakeContext(chain, locator, transientPolicies)创建了BuilderContext类型的对象context。
private IBuilderContext MakeContext(IBuilderStrategyChain chain,
IReadWriteLocator locator, params PolicyList[] transientPolicies)
{
PolicyList policies = new PolicyList(this.policies);
foreach (PolicyList policyList in transientPolicies)
policies.AddPolicies(policyList);
return new BuilderContext(chain, locator, policies);
}
....................先到这儿.......................................