装饰模式作为一个比较常见的模式,我就不再细说了,并且在.net框架中也多次出现(例如全透明装饰类BufferedStream与它的基类Stream)
此外,另一个比较有趣的例子大家可能不太会用到,这次被装饰的Type这个类型,装饰者是TypeDelegator。
Type这个类型本身比较复杂,大大小小的实例成员加起来不下100个(包括继承的),其中可覆盖的核心方法有也有50多个。如果需要装饰一下Type的话,那么至少这50多个成员需要被覆盖,而需求可能仅仅只是为了监视某一个属性。这使得手段和目的显得极为不对称。
面对这样的需求,M$提供了这么一个TypeDelegator类型来作为对Type装饰的基类,具体的装饰类只要再次覆盖其中的几个特定的方法就可以了。
这时候,应该可以想到,装饰模式中的基础接口应该尽量的轻,像Type这样的重型类似乎不太合适。但是有的时候显然有是需要装饰一下这种重型类。比如说,如果希望在反射某一个Type的时候,当取这个类型的所有实例方法(Type.GetMethod(BindingFlag.Instance))的时候记录一下日志。这样的需求似乎摆明了要用装饰模式。假设没有TypeDelegator类型(或者不知道它的存在
),在装饰Type的时候,估计就会火冒三丈的大骂M$,怎么在Type里面放了这么多垃圾方法,或者再找其他的手段来达到这个目的。
下面进入正题:大接口怎么装饰?
这里说的大接口(这里的接口是泛意的接口)是指需要覆盖的成员不下20个,而真正需要装饰的成员仅仅是很小一部分。这样要么和M$一样,建一个装饰基类。但是如果仅仅只有一个装饰类,岂不是很浪费。
那么第二条路是什么?经过漫长的思考,我觉得如果将大接口限定为狭义接口(interface,不包含抽象类等)的话,可以通过Emit技术来生成这么一个动态装饰类。
问题是:哪些成员需要装饰?这些成员怎么装饰?Debug怎么办?怎样半透明装饰?
经过否决N个方案后,终于找到了可行的方案。用到的核心技术依然是.net的四大神器:泛型、反射、特性、Emit。其中特性是可选的,通过配置注入也是完全可行的。
方案的核心手段是建立一个混入类(Mixin),这个混入类的职责是告诉动态装饰工厂如何装饰,并且需要绝对实现半透明的部分。
举个例子来说吧:
原始接口:
public
interface
ISource
{
void
HelloWorld();
string
Name {
get
;
set
; }
}
装饰接口:
public
interface
IDecorator
: ISource
{
event
EventHandler NameChanged;
}
混入类:
[DecoratorMixinType(
typeof
(ISource),
typeof
(IDecorator))]
public
class
DecoratorMixin
{
private
ISource _src;
private
IDecorator _decorator;
public
DecoratorMixin(ISource src, IDecoratorForTest decorator)
{
_src
=
src;
_decorator
=
decorator;
}
[DecoratorMixinProperty()]
public
string
Name
{
get
{
return
_src.Name; }
set
{
if
(value
!=
_src.Name)
{
_src.Name
=
value;
OnNameChanged(EventArgs.Empty);
}
}
}
protected
virtual
void
OnNameChanged(EventArgs e)
{
if
(NameChanged
!=
null
)
NameChanged(_decorator, e);
}
[DecoratorMixinEvent()]
public
event
EventHandler NameChanged;
}
再来看一下ISource的实现:
public
sealed
class
SrcImpl
: ISource
{
#region
Fields
private
string
_name;
#endregion
#region
Ctors
public
SrcImpl()
{
_name
=
this
.ToString();
}
#endregion
#region
ISrcForTest Members
public
void
HelloWorld()
{
Console.WriteLine(
"
Hello world, my name is {0}.
"
, Name);
}
public
string
Name
{
get
{
return
_name; }
set
{ _name
=
value; }
}
#endregion
}
最后,看一下客户端的调用:
class
Program
{
static
void
Main(
string
[] args)
{
ISource src
=
new
SrcImpl();
IDecorator decorator
=
DecoratorFactory.Create
<
ISource, IDecorator, DecoratorMixin
>
(src);
decorator.HelloWorld();
decorator.NameChanged
+=
new
EventHandler(decorator_NameChanged);
decorator.Name
=
"
Name2
"
;
decorator.HelloWorld();
Console.ReadLine();
}
static
void
decorator_NameChanged(
object
sender, EventArgs e)
{
IDecorator decorator
=
sender
as
IDecorator;
Console.WriteLine(
"
I'm \
"
Program\
"
, some one told me, the source changed it's name to {0}
"
, decorator.Name);
}
}
来看一下最终结果:
Hello world, my name
is
TestDynamicDecorator.SrcImpl.
I
'
m "Program", some one told me, the src changed it
'
s name to New Name
Hello world, my name
is
New Name.
在这里,魔术师就是
DecoratorFactory.Create
<
ISource, IDecorator, DecoratorMixin
>,它负责变出一个匿名类来实现
IDecorator,并且将其中的Name属性绑定的方法和NameChanged事件绑定的方法覆盖为调用混入类的方法,而其他方法(例如HelloWorld方法)就直接调用ISource对象的方法。
(目前这个
DecoratorFactory还未完成,主要是容错方面的问题,完成后将集成到ZForce中一齐发布
)