装饰模式作为一个比较常见的模式,我就不再细说了,并且在.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),这个混入类的职责是告诉动态装饰工厂如何装饰,并且需要绝对实现半透明的部分。
举个例子来说吧:
原始接口:
装饰接口:
混入类:
再来看一下ISource的实现:
最后,看一下客户端的调用:
来看一下最终结果:
在这里,魔术师就是DecoratorFactory.Create<ISource, IDecorator, DecoratorMixin>,它负责变出一个匿名类来实现IDecorator,并且将其中的Name属性绑定的方法和NameChanged事件绑定的方法覆盖为调用混入类的方法,而其他方法(例如HelloWorld方法)就直接调用ISource对象的方法。
(目前这个DecoratorFactory还未完成,主要是容错方面的问题,完成后将集成到ZForce中一齐发布)
此外,另一个比较有趣的例子大家可能不太会用到,这次被装饰的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; }
}
{
void HelloWorld();
string Name { get; set; }
}
装饰接口:
public interface IDecorator
: ISource
{
event EventHandler NameChanged;
}
: 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;
}
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
}
: 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);
}
}
{
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.
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中一齐发布)