ObjectBuilder2.0 的学习
ObjectBuilder概念
ObjectBuilder2提供了一种具有高可扩展性的、基于策略(Strategy Based)的对象创建框架,它不仅仅是Unity的基础组件,也是EnterLib4.1核心组件
注:这里讨论的是ObjectBuilder2,下文简称为OB2。ObjectBuilder1的知识请大家参看黄忠成先生的Object Builder Application Block,讲的非常的详细,对我启发很大。
OB2和DI
OB2 通常被描述为Dependency Injection(依赖注入)的工具。但是看它的代码,很难明白用它到底是怎么做依赖注入的,反而会陷入到一些概念里边,如 locators,strategies,builders等等,弄不清楚到底什么是创建的关键?OB2有时还会被描述为创建依赖注入容器的框架,这个说法也不全面,大家会认为,是不是必须在OB2上面做一些东西才能实现依赖注入?OB2的一部分实际上已经实现了一个可用的依赖注入版本,如果想弄明白怎么实现依赖注入功能的,那么就需要读懂OB2的源码,但是如果用依赖注入的思想去读源码,那么又会很快迷失在那些概念中。是不是听起来很迷惑?
迷惑的地方在于OB2到底是用来做什么的?我觉得实际上OB2不是一个依赖注入的容器,也不是一个依赖注入的框架,它实际上只是一个可配置的对象工厂,是一个用于创建对象的基础组件,它的功能仅仅就是可以根据需要创建对象。
注:关于依赖注入的概念请大家参看MartinFowler的文章,http://www.martinfowler.com/articles/injection.html
对象工厂和OB2的由来
上面说到OB2是一个对象工厂,那么什么是对象工厂呢?直接地说就是,它根据要求的类型,给出一个实例。设计模式涉及了3个不同的对象工厂,抽象工厂,工厂方法,建造者模式。
对于一个没有经验的开发人员来说,工厂是一个不被欣赏的概念。为什么不能用new来替代工厂?new一个实例不是很方便么?最根本的原因就是耦合。当用new来创建对象的时候,肯定会硬编码了具体的类。举个简单的例子:
public decimal CountryCalculateTaxes(People people)
{
CountryTaxCalculator taxCalculator = new CountryTaxCalculator();
return taxCalculator.CalculateTaxFor(people);
}
这段代码看着很不错,但是如果需求变化了,还需要支持到省直辖市的税收,那怎么办?就像这样:
public decimal BeijingAndCountryCalculateTaxes(People people)
{
BeijingAndCountryTaxCalculator taxCalculator = new BeijingAndCountryTaxCalculator();
return taxCalculator.CalculateTaxFor(people);
}
看起来还不错,但是我们需要30多个省的版本,为每个省直辖市做一个。通常这时候会定义接口,然后在实现这个接口。
public interface ITaxCalculator
{
decimal CalculateTaxFor(People people);
}
public decimal CalculateTaxes(People people)
{
ITaxCalculator calculator = new CountryTaxCalculator();
return calculator.CalculateTaxFor(people);
}
但是实际上接口并没有解决我们的问题。问题依然是new,依然硬编码,依然要和创建的各个版本的Calculator紧密联系。这时候工厂得到了应用:
public decimal CalculateTaxes(People people)
{
TaxCalculatorFactory factory = new TaxCalculatorFactory();
ITaxCalculator calculator = factory.CreateCalculator();
return calculator.CalculateTaxFor(people);
}
TaxCalculatorFactory对象包括了创建TaxCalculator对象的所有细节,它可以让客户代码根据需要创建对象,同时客户代码不关心也不知道它是如何创建的。
在现在的系统中,我们经常会写一些类似于上面的工厂。在P&P开发Enterprise Library和CAB等应用的时候用了许多这样儿的重复代码,这些重复的创建对象的代码最终导致了OB2的出现。OB2实际上更像是一个泛型的对象工厂。
注:P&P微软的一个开发组,大家可以去www.codeplex.com上去看他们开发的许多组件等。
用OB2创建对象
通过上面,已经知道了OB2是一个创建对象的泛型对象工厂。那么OB2是怎么创建对象的?通过下面的例子一步一步理解,首先看一下OB2的几个主要的类:
Builder:是创建对象的入口,但是实际上不做创建工作,只是组装一些对象,通过StrategyChiain来创建对象。
Strategy Chain:一个排序好的strategy对象的集合,Strategy Chain保证Strategy对象按顺序执行。
Strategy:负责创建对象的一部分工作。
Policy:Strategy用Plicy对象在Strategy Chain传递信息并且帮助Strategy完成一些个性化创建工作。
下面举一个具体的例子来理解这些类的作用。
入口点
public interface IBuilder
{
object BuildUp(IReadWriteLocator locator,
ILifetimeContainer lifetime,
IPolicyList policies,
IStrategyChain strategies,
object buildKey,
object existing);
TTypeToBuild BuildUp<TTypeToBuild>(IReadWriteLocator locator,
ILifetimeContainer lifetime,
IPolicyList policies,
IStrategyChain strategies,
object buildKey,
object existing);
TItem TearDown<TItem>(IReadWriteLocator locator,
ILifetimeContainer lifetime,
IPolicyList policies,
IStrategyChain strategies,
TItem item);
}
OB2的入口点是基于IBuilder接口的,通过BuildUp方法来创建对象。一个Builder对象有一个Policy对象集合和 Strategy对象集合,Builder通过他们来创建对象。在这个接口中有许多的细节,通过下面的过程来逐一理解。
实现一个创建对象的工厂,这里用泛型编程,类似于
public class BasicFactory
{
public T Create<T>()
{
return null;
}
}
通过调用Builder来实现这个工厂。记得上面的IBuilder接口吧,我们需要创建一个Builder实例然后调用BuildUp方法就可以实现创建对象。但是BuildUp有好几个参数。先忽略掉IReadWriteLocator,和ILifetimeContainer,传值null给它们,这不会影响对象的创建,后面再来理解它们的用处,但是必须要有一个StrategyChain,并且其中必须有一个Strategy。
在OB2中Strategy对象负责具体的创建工作。Builder对象调用一组Strategy对象来完成创建,这些Strategy实现了 IBuilderStrategy接口:
public interface IBuilderStrategy
{
void PreBuildUp(IBuilderContext context);
void PostBuildUp(IBuilderContext context);
void PreTearDown(IBuilderContext context);
void PostTearDown(IBuilderContext context);
}
只有四个方法,看起来不是很复杂。PreBuildUp和PostBuildUp在调用IBuilder.BuildUp时,被调用。 PreTearDown和PostTearDown在对象被释放的时候调用。
参数IBuilderContext包括了所有参与创建过程的信息。这里主要说说buildkey和existing对象,buildkey对象提供要创建对象的类型和id信息,existing对象存放每个策略执行PreBuildUp方法创建完的对象,通过existing对象传递到下一个策略中,并且最终返回的existing对象既是要创建的对象。
创建一个基本的创建Strategy。
public override void PreBuildUp(IBuilderContext context)
{
base.PreBuildUp(context);
object result = context.Existing;
if (result == null)
{
NamedTypeBuildKey key = (NamedTypeBuildKey)context.BuildKey;
Type typeToBuild = key.Type;
context.Existing = Activator.CreateInstance(typeToBuild);
}
}
把Strategy插入到Strategy Chain中
现在有了Strategy,需要把这个Strategy插入到strategy chain中,我们的BasicFactory需要用这个Strategy,最简单的办法是把它加入到构造器中:
public class BasicFactory
{
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();
private PolicyList policies = new PolicyList();
public BasicFactory()
{
strategies.AddNew<BasicCreationStrategy>(BuilderStage.Creation);
}
……
}
通过调用strategies.AddNew方法来创建BasicCreationStrategy对象, 注意BuilderStage.Creation参数,这个参数定义了Strategy在Strategy Chain中的执行顺序,BuilderStage是一个枚举类型,包括了PreCreation,Creation,Initialization和 PostInitialization,每个Strategy仅在一个Stage中,将按照BuilderStage的顺序执行,如果两个 Strategy在同一个Stage中按照添加的顺序执行。继续完善BasicFactory。
public class BasicFactory
{
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();
private PolicyList policies = new PolicyList();
public BasicFactory()
{
strategies.AddNew<BasicCreationStrategy>(BuilderStage.Creation);
}
public T Create<T>()
{
Builder builder = new Builder();
return builder.BuildUp<T>(
null,
null,
policies,
strategies.MakeStrategyChain(),
NamedTypeBuildKey.Make<T>(null),
null);
}
}
在这个例子中,只有一个Strategy,通过它可以创建对象。这是OB2的一个最简单的应用。
写个单元测试,应该可以测试通过了吧。
[TestMethod]
public void ShouldCreateObjectGivenRuntimeType()
{
BasicFactory factory = new BasicFactory();
Customer result = factory.Create<Customer>();
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof(Customer));
}
class Customer
{
private string firstName;
private string lastName;
public Customer()
{
}
public Customer(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
用Policies传递信息
现在用BasicFactory可以创建任意类型的对象了,但是这个对象的构造器参数怎么办?比如测试驱动是这样的:
[TestMethod]
public void ShouldCreateObjectWithParameters()
{
BasicFactory factory = new BasicFactory();
Customer result = factory.Create<Customer>("John", "Doe");
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof (Customer));
Assert.AreEqual("John", result.FirstName);
Assert.AreEqual("Doe", result.LastName);
}
通过上面的代码,可以创建这样的Create方法:
public T Create<T>(params object[] constructorArgs)
{
Builder builder = new Builder();
return builder.BuildUp(null, null,
policies,
strategies.MakeStrategyChain,
NamedTypeBuildKey.Make<T>(null),
null);
}
现在虽然把构造器参数传递到了BuildUp方法,但是在哪儿存放它们,strategy怎样得到它们。如果看过了BuildUp的定义,第一个参数是 locator对象,这是一个可以存放的地方,locator可以让Strategy根据任意key查找任何对象。然而Locator对象通常被用来保存生命周期比一次调用BuildUp周期长的对象。所以locator不推荐使用。所以需要把这些参数存放到其它地方,即Policy对象中。Policy 给Strategy提供了一个可以保存额外信息的机制。在我们的例子中需要保存构造器参数,使strategy可以找到并使用它。
一般先实现一个policy的接口,然后再提供多个实现。
public interface ICreationParameterPolicy : IBuilderPolicy
{
object[] Parameters { get; }
}
public class CreationParameterPolicy : ICreationParameterPolicy
{
private object[] parameters;
public CreationParameterPolicy(params object[] parameters)
{
this.parameters = parameters;
}
public object[] Parameters
{
get { return parameters; }
}
}
现在已经有了保存构造器参数的地方,下面要在strategy中使用它。Policy通过Context来传递给PreBuildUp方法。
public override void PreBuildUp(IBuilderContext context)
{
base.PreBuildUp(context);
object result = context.Existing;
if (result == null)
{
NamedTypeBuildKey key = (NamedTypeBuildKey)context.BuildKey;
Type typeToBuild = key.Type;
ICreationParameterPolicy policy =
context.Policies.Get<ICreationParameterPolicy>(typeToBuild);
if (policy != null)
{
context.Existing = Activator.CreateInstance(typeToBuild, policy.Parameters);
}
else
{
context.Existing = Activator.CreateInstance(typeToBuild);
}
}
}
通过context.Policies.Get方法来查找这个policy。它有一个泛型参数,这个参数是要查找的policy的类型,并且通过一个 key来确定是哪个具体的policy。看到这儿我们就知道了,将通过build key来查找刚才的policy。如果policy对象在context中,我们最终可以把构造器参数传递给 Activator.CreateInstance。那么怎么把这个policy存放到context中呢?
看看BuildUp的第三个参数policies,这个Policy list将被传入到BuildUp调用中。在我们的BasicFactory中已经有一个policy list了,所以可以把policy保存在其中。但是那不是我们最终希望的,要记住一点,特定的policy应该仅仅是给当前的BuildUp调用的,如果把它存放到成员变量中,它将跨越每次create调用。同时PolicyList类支持层次结构。当建立了一个PolicyList的时候,它可以有 InnerPolicyList。如果在当前的PolicyList查找失败,它可以继续查找它的InnerPolicyList。这就提供了持久的 policy和短暂的policy。持久的policy可以加入到BasicFactory自己的成员变量中就象strategy似的,Builder对象将每次create都使用它。短暂的policy仅仅是每次BuildUp的生命周期中使用。在这个例子中因为构造器参数每次都不同,所以使用短暂的 policy。BasicFactory.Create的具体实现如下:
public T Create<T>(params object[] constructorArgs)
{
Builder builder = new Builder();
PolicyList transientPolicies = new PolicyList(policies);
transientPolicies.Set<ICreationParameterPolicy>(new CreationParameterPolicy(constructorArgs), NamedTypeBuildKey.Make<T> (null).Type);
return builder.BuildUp<T>(null, null,
transientPolicies,
strategies.MakeStrategyChain(),
NamedTypeBuildKey.Make<T>(null),
null);
}
通过上面的例子用OB2建立了一个简单的有构造器参数的对象。到此为止,我们明白了Builder,Strategy,Policy的基本用途。用OB2 可以创建简单的对象。但是OB2还可以通过多个strategy组成一个链表来创建更加复杂的对象。
一个很普遍的例子就是缓存对象,当第一次需要时,创建此对象,创建的对象耗费很大,要读数据库或者调用WCF服务等等。所以不想每次创建都要耗费资源,所以存储这个对象的副本,以后可以直接使用这个对象的副本。看看用OB2怎么来建立这么一个具有缓存功能的对象工厂。
下面还是先写个测试驱动,需求是想创建一个还没有的对象,但是第二次创建的时候希望直接找回同样的对象实例。因为可能要获取的多个Customer对象都需要缓存,所以要能够通过id标示它们。
[TestMethod]
public void ShouldGetCachedObjectSecondTime()
{
CachingFactory factory = new CachingFactory();
factory.SetCached<Customer>(true, "Jane");
Customer c1 = factory.Get<Customer>("Jane", "Jane", "Doe");
Customer c2 = factory.Get<Customer>("Jane");
Assert.AreSame(c1, c2);
}
我们的工厂可以通过SetCached方法来指定哪个类型要被缓存哪个不需要,Get方法即可以创建想要的对象也可以找到以前已经创建过的。注意两个方法都有一个string类型的参数,表示要缓存对象的ID。调用的第二个Get方法没有构造器参数,只有ID。只要ID是唯一的,值是什么都无所谓甚至可以是null。
那怎么实现它,能想到用strategy和policies,不过还需要其它东西一起来实现它。
Build Key
前面对象工厂BasicFatory用的buildkey只有类型,而没有保存ID。现在要实现缓存,那么就必须也要保存 ID了。看一下实现了IBuildKey接口的NamedTypeBuildKey类,此类通过type和id共同建立一个key供OB2创建的对象使用。
public struct NamedTypeBuildKey : IBuildKey
{
private Type type;
private string name;
public NamedTypeBuildKey(Type type, string name)
{
this.type = type;
this.name = !string.IsNullOrEmpty(name) ? name : null;
}
public NamedTypeBuildKey(Type type)
: this(type, null)
{
}
……
}
大家注意build key被定义成了一个struct而非class。
缓存在哪儿
工厂创建完对象,为了下次可以返回已经存在的对象,需要把它存放在一个地方以便下次可以查找出来。OB2已经提供了这么一个容器。
看看上面的IBuilder.BuildUp方法。它的第一个参数,locator,它是干什么用的?到现在,我们一直传的值是null。现在看看 IReadWriteLocator能为我们做些什么。非常简单,一个locator就是一个dictionary。当把一个对象和一个key放进去,然后用同样的key还可以把它取出来。IReadWriteLocator接口支持多个方法来查询locator中的内容。
OB2包含了一个IReadWriteLocator的实现,Locator,它实现了一个弱引用dictionary。弱引用是一个会被GC回收的引用。这个设计正好符合我们缓存的要求;如果我们正在使用一个缓存对象,它是可用的,但是当内存压力过大时,GC将清理这些仅仅被locator引用的对象。
既然已经知道缓存对象工厂需要一个locator,那么来试着实现这个工厂CachingFactory:
public class CachingFactory
{
private IReadWriteLocator cache;
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();
private PolicyList policies = new PolicyList();
public CachingFactory()
{
cache = new Locator();
}
public T Get<T>(string id, params object[] constructorParams)
{
return (T)(new Builder().BuildUp(
cache,
null,
CreateConstructorParameterPolicy(
typeof(T), id, constructorParams),
strategies.MakeStrategyChain(),
NamedTypeBuildKey.Make<T>(id),
null));
}
public void SetCached<T>(bool shouldCache)
{
SetCached<T>(shouldCache, null);
}
private PolicyList CreateConstructorParameterPolicy(Type typeToCreate, string id,object[] parameters)
{
PolicyList policies = new PolicyList(this.policies);
policies.Set<ICreationParameterPolicy>(
new CreationParameterPolicy(parameters),
new NamedTypeBuildKey(typeToCreate, id));
return policies;
}
}
将locator作为成员变量,每次调用BuildUp都会将其传入,用NamedTypeBuildKey作为key。下面来继续实现 SetCached方法。
我们需要把构造器参数传入到strategy中,前面用了policy对象。这个policy对象是短暂的,因为每次调用工厂的create需要传入不同的构造器参数。另一方面,缓存的设置是需要跨每次create调用的。所以需要一个持久的policy list,并且把这个新的caching policy加入到成员变量policy list中。
定义这个policy很简单。前面已经知道PolicyList通过build key来匹配policy。这样我们的policy只需要指出是否需要缓存即可。接口和实现可以设计成这样:
public interface ICachingPolicy : IBuilderPolicy
{
bool ShouldCache { get; }
}
class ShouldCachePolicy : ICachingPolicy
{
public bool ShouldCache
{
get { return true; }
}
}
class ShouldNotCachePolicy : ICachingPolicy
{
public bool ShouldCache
{
get { return false; }
}
}
那么CachingFactory即可实现成:
public class CachingFactory
{
private IReadWriteLocator cache;
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();
private PolicyList policies = new PolicyList();
private ICachingPolicy shouldCachePolicy = new ShouldCachePolicy();
private ICachingPolicy shouldNotCachePolicy = new ShouldNotCachePolicy();
……
public void SetCached<T>(bool shouldCache, string id)
{
ICachingPolicy cachingPolicy = shouldNotCachePolicy;
if (shouldCache)
{
cachingPolicy = shouldCachePolicy;
}
policies.Set<ICachingPolicy>(cachingPolicy, NamedTypeBuildKey.Make<T>(id));
}
……
}
这块的重点在于policies.Set,这里将policy保存到了持久的policy list中,调用BuildUp的时候自动传入到strategy chain中。通过这个持久的policy list能更好的理解policy的层次结构,即创建对象的时候它会在policy list中逐层寻找所需要的policy。同时用build key来保存policy,以便后面取出使用。
Strategy和前面的合并
现在builder能告诉我们这个对象是否应该被缓存。下面需要增加一个strategy来实现缓存了。创建的逻辑类似于:
If 对象应该被缓存:
If 对象已经在locator中,返回它
Else:
创建对象
在locator中保存对象
返回创建的对象
前面已经实现了对象创建的strategy,我们想复用刚才的strategy,那么我们只需要实现保存和查找两步,我们将分两个 strategy来实现它们。这是很合理的,因为他们发生在不同的时间,应该先实现查找缓存对象:
public class CacheRetrievalStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
base.PreBuildUp(context);
ICachingPolicy cachePolicy = context.Policies.Get<ICachingPolicy>(context.BuildKey);
if (cachePolicy != null)
{
if (cachePolicy.ShouldCache)
{
object cached = context.Locator.Get(context.BuildKey);
if (cached != null)
{
context.Existing = cached;
context.BuildComplete = true;
}
}
}
}
}
看上面的代码有个关键的地方是context.BuildComplete = true; 这个BuildComplete表示对象创建是否完成,这里如果在locator中已经找到要创建的对象那么就认为对象已经创建完成,后面的 strategy的PreBuildUp就可以不继续执行了。
再看看怎么实现存储保存对象到locator。
public class CacheStorageStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
base.PreBuildUp(context);
ICachingPolicy cachePolicy = context.Policies.Get<ICachingPolicy>(context.BuildKey);
if (cachePolicy != null)
{
if (cachePolicy.ShouldCache)
{
context.Locator.Add(context.BuildKey, context.Existing);
}
}
}
}
现在有了全部的strategy,需要把它们加入到builder中。 继续用builder stage来保证strategy按正确的顺序来依次执行,像下面这样:
public CachingFactory()
{
strategies.AddNew<CacheRetrievalStrategy>(BuilderStage.PreCreation);
strategies.AddNew<BasicCreationStrategy>(BuilderStage.Creation);
strategies.AddNew<CacheStorageStrategy>(BuilderStage.PostInitialization);
cache = new Locator();
}
这样就完成了整个CachingFatory的设计,又弄清楚了几个关键点,build key,locator,持久的policy list的作用都是什么。
还有最后一个参数LifeTimeContainer,它一般用于保存已经创建的对象,不需要build key,它只是永久保存已经创建的对象以便以后使用,只要是相同类型的对象存入到LifeTimeContainer中,每次取出来的都是同一个对象。利用LifeTimeContainer我们可以实现singleton模式。
总结
OB2创建对象的整体流程
先整体看一下OB2创建对象的过程,当用OB2创建对象的时候,请记住下图。
OB2是通过一系列动作来创建对象的,这一系列动作在OB2中称为策略(strategy),这些策略按照它们加入到创建过程中的顺序形成一个策略链,然后一个一个按顺序执行。因为在创建过程中,需要将一些信息由一个动作(策略)传递到下一个,并且一些策略需要有特别的工作要做,所以需要一个容器来保存这些信息(状态信息,参数,无论什么),并完成这些特别的工作。这个容器是一个集合,这个集合被称作policy list。在这个集合中的Policy可以是我们希望的任何东西,实际上在OB2中它的接口(IPolicy)是一个空接口。然而policy不是持久的,它的生命周期仅和创建过程的周期相同,当创建对象的过程结束时,它也就随之销毁。所以如果希望当创建过程结束后仍然保存一些信息,就需要另外一个集合,OB2中被称为Locator。Locator是一个容器,它确保能够通过key随时查找到需要的信息,要注意的是Locator是一个弱饮用的 dictionary,所以保存得内容要可以随时重新创建。最后一个关键部分,对象一旦被创建完成(既策略链执行的最后结果),它可以被保存在一个容器中,这个容器保存所有创建完成的对象以备以后使用,这个容器在OB2中被称为LifeTimeContainer。
大家要记住OB2是一个对象工厂,而Unity(微软提供的一种IOC容器)可以被看作是一个OB2的门面,它用到了多个OB2的特性。举个例子,LifeTimeContainer可以确保创建的对象是singleton模式的,但是在OB2中默认的LifeTimeContainer不对这个对象做任何事情,只是提供了一个可以管理它的地方。同样的还有policy,locator,都需要我们去灵活使用它们。
当用OB2创建对象时,Builder类是整个创建对象的入口点,BuilderContext类是一个上下文,它负责包装要创建对象所需要的一切信息(strategy,policy,locator,lifetimecontainer等)。实际上,通过下面的代码,可以看到,这些信息通过简单的重组,然后通过BuildUp方法调用策略链(从第一个策略开始)创建对象。
public object BuildUp(IReadWriteLocator locator,
ILifetimeContainer lifetime,
IPolicyList policies,
IStrategyChain strategies,
object buildKey,
object existing)
{
BuilderContext context = new BuilderContext(strategies, locator, lifetime, policies, buildKey, existing);
return strategies.ExecuteBuildUp(context);
}
下面是BuilderContext的接口代码,可以看到它包装了哪些信息:
public interface IBuilderContext
{
IStrategyChain Strategies { get; }
ILifetimeContainer Lifetime { get; }
IReadWriteLocator Locator { get; }
object OriginalBuildKey { get; }
IPolicyList PersistentPolicies { get; }
IPolicyList Policies { get; }
IRecoveryStack RecoveryStack { get; }
object BuildKey { get; set; }
object Existing { get; set; }
bool BuildComplete { get; set; }
IBuilderContext CloneForNewBuild(object newBuildKey, object newExistingObject);
}
OB2的主要成员
Builder – 对象创建过程的主入口点。它实际上不创建任何对象,只是装配一些对象,然后通过调用Strategy Chain来创建对象。
Locator – 是一个dictionary,如果在创建对象过程中,需要存储一些将来要用到的信息。如果需要的信息超出了创建对象过程的生命周期建议使用 Locator。正如前面提到的,OB2是一个框架,所以,如果希望用Locator作为每次创建对象需要的一种存贮容器,需要创建更多的基础组件。 Locator的dictionary是一个弱引用对象,所以当性能需要时,它将被GC回收,所以locator中存储的信息,是要求可以随时重新创建的,例如Cache Application的应用。
LifetimeContainer – 是一个枚举集合,因此它能够存贮被创建的所有对象。同样,LefetimeContainer本身不做任何事情,我们需要自己控制什么对象要被存储,什么时候释放。例如,如果要求一个对象实现singleton模式,就需要写一些代码确保这个对象存贮在LifetimeContainer中是唯一的,同时如果一个singleton对象以前已经被创建过,那么需要一个singleton策略打断创建过程。
PolicyList – Policy的集合,Policy的作用在于保存在策略中传递的信息并且给策略提供个性化的动作,仅在对象创建过程中存在。生命周期仅和创建过程相同。例如,如果构造器的参数需要被加入到策略链中,那么Policy List的各个Policy可以存储它们。
Chain Strategy – 动作(策略)的集合,动作将一个一个顺序执行直到最后一个动作完全包装好要创建的对象。
下面将补充介绍一些上面没有仔细讲过的要点。
Strategy Chain策略链
一个策略就是一个动作发生的地方,但是为什么要有一个动作的链呢?这个设计类似于职责链模式(Chain of Responsibility)。经过检查,如果需要在这个策略中处理那么就处理,如果不需要传递到策略链中的下一个策略处理。实际上,在创建过程中,链中的策略被经过两次。一次是从头到尾的调用每个策略中的PreBuildUp方法,另一次是从尾到头的调用每个策略的PostBuildUp方法。这样就允许策略可以做一些创建后的处理,即使它是由其他策略创建的。
策略通过BuilderContext得到需要的所有参数,如下代码:
public interface IBuilderStrategy
{
void PreBuildUp(IBuilderContext context);
void PostBuildUp(IBuilderContext context);
void PreTearDown(IBuilderContext context);
void PostTearDown(IBuilderContext context);
}
如果想打断策略链或者策略决定创建已经完成要怎么办?Context有个一个属性BuildComplete,如果将它设置为true,则打断策略链的循环。示例,一个最简单的策略,不做任何事情,只是简单打断创建:
class FirstStrategy : BuilderStrategy
{
public override void PreBuildUp(IBuilderContext context)
{
Console.WriteLine("First strategy, prebuilding " + context.OriginalBuildKey);
context.BuildComplete = true; //this will stop the chain of strategies
}
public override void PostBuildUp(IBuilderContext context)
{
Console.WriteLine("First strategy, postbuilding " + context.OriginalBuildKey);
}
}
当然这个示例的结果什么也没有创建,但是这个例子可以看到即使某个策略打断了创建,反方向的循环依旧会执行此策略以前所有执行过的策略的 PostBuildUp方法。下面的代码展示了在StrategyChain类中怎样执行创建的。
public object ExecuteBuildUp(IBuilderContext context)
{
int i = 0;
try
{
for(; i < strategies.Count; ++i)
{
if(context.BuildComplete)
{
break;
}
strategies[i].PreBuildUp(context);
}
if(context.BuildComplete)
{
--i; // skip shortcutting strategy's post
}
for(--i; i >= 0; --i)
{
strategies[i].PostBuildUp(context);
}
return context.Existing;
}
catch(Exception ex)
{
context.RecoveryStack.ExecuteRecovery();
throw new BuildFailedException(
strategies[i], i, context.BuildKey, ex);
}
}
LifetimeContainer
LifeTimeContainer是一个在构建对象过程中,使用的对象容器,它一般用于保存已经创建的对象。提供了对象集合 IEnumable接口实现和一系列的对象保存方法,如创建的对象是singleton模式,那么此对象就可以保存在LifeTimeContainer 中。LifetimeContainer支持IDisposable接口,以便释放资源。在OB2中已经实现了singleton模式需要的 strategy和policy,大家可以直接使用。
public interface ILifetimeContainer : IEnumerable<object>, IDisposable
{
int Count { get; }
void Add(object item);
bool Contains(object item);
void Remove(object item);
}
第一次写这样儿的文章,查看了一些国外的文章资料,国内介绍OB2的资料真的不多,希望大家多提宝贵意见,我好及时改正,愿和大家一起共同进步。下一篇文章希望和大家一起讨论EnterpriseLibrary4.1的Configuation,Configuration也是 EnterpriseLibrary核心的三大组件(OB2、Configuration,Instrumentation)之一,通过它的配合,EnLib可以通过配置文件灵活的创建各个Application Block。希望通过这个系列文章和大家一起把EnterpriseLibrary的各个Block的设计和实现研究清楚,以便更好的在自己的框架中使用。
附件:文中的两个工厂demo https://files.cnblogs.com/zranran/ClassLibrary1.rar