微软objectBuilder解读:构造一个轻量级的Dependency Injection容器(3) Locator(转)

http://www.cnblogs.com/huyq2002/archive/2011/05/23/2054189.html

 

在上一节我们做了一个简单的容器,基本实现了类型/对象的注册和查询,main里面的代码现在比较稳定,无需再与各种工厂实例打交道,也无需了解各种接口和工厂的对应关系,但是我们实现的容器存在一些问题:

1)      每种接口类型只能注册一个对应组件实例(接口的类型就是Dictionary里面的key, key不能重复,微软ObjectBuilder的Locator利用了键/组件Id来注册key)

2)      Container只有一个层次,微软ObjectBuilder的Locator实现了多层次的链表结构的Container,后面我们再来分析这样的层次结构的Container的优缺点

   

3)      Container里面引用的组件实例都是强引用,会导致内存泄露的问题。

举个简单的例子,我们的main的代码很长、或者执行时间很长,在main里面最开始的代码我们从Container里面注册并使用了支持接口IFirst的组件,此后再也未使用过此组件,Container对象在我们的代码里面生命周期非常长,由于Container里面的Add的方法导致我们的inner字典一直保持对这个组件的强引用,从而使垃圾回收器无法正常对该组件进行内存清理,产生内存泄露。

¨         内存泄露

在.net中产生内存泄露的原因有很多种,例如:错误的使用Dispose模式,短生命周期的对象保持对长生命周期对象的强引用,事件里面采用+=方法注册了事件处理函数但是未采用-=注销事件函数…

关于.net内存泄露的概念和检测可以参考MSDN .net CLR Profile和StackOverflow里面的示例。

http://msdn.microsoft.com/en-us/library/ff650691.aspx

http://stackoverflow.com/questions/20386/memory-leaks-in-net

¨         强引用/弱引用

关于weakReference的概念,请参考以下链接

http://msdn.microsoft.com/en-us/library/system.weakreference.aspx

WeakReference的关键就是指对象之间的引用不会影响正常的垃圾回收,在Java里面也有WeakReference类。

 

那么我们如何针对上述问题修改我们的Container呢?

1)      修改inner对象,将它变为弱引用的键/对象。

Dictionary<TKey, WeakReference> inner;

2)      由于引入WeakReference,原有的Dictionary的代码不能满足Container的需要,必须定制自己的Dictionary。其主要原因是Dictionary里面存储的对象可能已经是空(因为是弱引用,可以被垃圾回收),在读取前必须做为空处理。

3)      扩展单一Container为链表,支持Container的层次关系。

4)      修改Key的类型,由Type类型改为object,支持同一接口可以注册多个实例类型,新的key为type+Id (这一点我们会在后续文章继续分析)。

5)      扩展Container的接口,将Container分为只读/读写,(想一下,为什么要这样设计呢)

下面我们就ObjectBuilder里面的Locator的代码进行分析。

 

1 WeakRefDictionary

WeakRefDictionary我们刚才分析了它其实就是对Dictionary<TKey, WeakReference>进行的封装,为什么不直接使用它的主要原因是因为引入了WeakFeference,在注册和注销的过程中必须考虑空对象的问题。

 其中

 1)EnCodeNullObject,DecodeNullObject就是注册实例为空处理的函数,它主要将null处理为我们自定义的空类型NullObject,在Add和TryGet里面被调用

 2)CleanAbandonedItems在Count方法里面被调用,它将会清除已经被垃圾回收的对象实例。

WeakRefDictionary同一般的Dictionary<TKey, TValue>一样,实现了Count, Add, Remove, ContainsKey, TryGet/TryGetValue, IEnumerable<KeyValuePair<TKey, TValue>>等方法和属性。

其实读者可以参考Dictionary的接口方法自己写出WeakRefDictionary的所以代码。我们以TryGet为例,   

复制代码
publicbool TryGet(TKey key, out TValue value)
{
value
=default(TValue);
WeakReference wr;
//直接调用Dictionary的TryGetValue
if (!inner.TryGetValue(key, out wr))
returnfalse;
//利用WekRef.Target活动对目标对象的引用
object result = wr.Target;
//目标实例为空处理
if (result ==null)
{
inner.Remove(key);
returnfalse;
}
//调用DecodeNullObject进行空对象的处理
value = DecodeNullObject<TValue>(result);
returntrue;
}
复制代码

 2 IReadableLocator 接口   

复制代码
publicinterface IReadableLocator:IEnumerable<KeyValuePair<object,object>>
{
int Count { get; }
IReadableLocator ParentLocator {
get; }
bool ReadOnly { get; }
bool Contains(object key);
bool Contains(object key, SearchMode options);
TItem Get
<TItem>();
TItem Get
<TItem>(object key);
TItem Get
<TItem>(object key, SearchMode options);
object Get(object key);
object Get(object key, SearchMode options);
IReadableLocator FindBy(Predicate
<KeyValuePair<object, object>> predicate);
IReadableLocator FindBy(SearchMode options, Predicate
<KeyValuePair<object, object>> predicate);
}
复制代码

  

IReadableLocator接口主要提取了我们上个例子的Container对象中间有关”读”的方法,并且增加了对Container的链表层次支持:IReadableLocator ParentLocator保存了对父容器的引用。SearchMode枚举支持了对层次的容器的级联搜索。

最后两个方法我们暂时不去理会,其他的方法很直观,可以参考我们自己写的Container对象 

 3 IReadWriteLocator 接口

IReadWriteLocator接口主要提取了我们上个例子的Container对象中间有关”写”的方法,非常简单

void Add(object key, object value);

bool Remove(object key); 

4 ReadableLocator (抽象类)

作为实现IReadableLocator的抽象类,它仅仅实现了一些关于多态/泛型的代码,如

TItem Get<TItem>(), TItem Get<TItem>(object key)等等,

这样设计的好处就是Locator的具体存储机制WeakRefDictionary和ReadableLocator的代码分离,Locator可以看作是Adapter模式的特例,内部包含了WeakRefDictionary的实例。读者完全可以利用其他类型的Dictionary来实现特定用途的Container而且代码改动很小。

 5 ReadWriteLocator (抽象类)

作为实现IReadWriteLocator的抽象类,继承自ReadableLocator抽象类,并且增加了有关”写”Container的方法

 6 Locator

继承了ReadWriteLocator,并且增加了对WeakRefDictionary的具体引用,实现了微软的Container。它主要的代码就是对WeakRefDictionary的封装,没有什么复杂的逻辑。

 微软为什么要这样繁琐的把接口分离为读/写,并且采用抽象类来封装代码呢?

主要原因有以下几种

1)  接口和实现分离,有更好的扩展性

2)  符合接口的单一职责设计原则

3)  类里面的代码看起来很简洁,直观,也便于单元测试。

这样的设计策略在.net framework中有很多例子。

至此,我们已经分析了微软针对我们的Container的缺陷而实现的优化的容器Locator,它的功能和Container一样,主要支持对象的注册和注销,为后面我们要讲的Builder实现了Dependency Injection的底层基础支持。

从Locator的代码看来,微软实现的Container很好的遵循了基于接口/抽象的编程等设计原则。

 ObjectBuilder 之Locator源代码

https://files.cnblogs.com/huyq2002/ObjectBuilder(Locator).zip

posted @ 2013-01-16 17:12  .NET Fans  阅读(197)  评论(0编辑  收藏  举报