我假设看这篇文章的朋友对装饰者模式都能有各自的、深入的理解。因为这篇文章是讨论装饰者模式的性能问题。
在本人的“.NET简谈设计模式之(装饰者模式)”一文中比较详细的讲解了装饰者模式的一般应用,但是我总是感觉装饰者模式隐隐约约之中有点不完美。经过我昨天一整天的思考、推敲终于找到了它隐隐约约中的那点不完美是什么,为了行为去继承带来的无辜的性能开销。所以本人想把它写出来,跟大家讨论下装饰者模式的性能该如何平衡。是用时间换空间还是用空间换时间,这里的时间就是我们开发的效率时间。
首先回顾一下装饰者模式诞生的本意是什么,它的官方意思是:动态地给一个对象添加一些额外的职责。我们都知道给对象扩展功能是通过继承来实现,但是继承有它的不好之处,比如:子类与父类之间的耦合、子类的无限扩大等等。而装饰者模式就是想利用动态的给需要扩展的对象添加功能。将需要扩展的动能独立起来,作为一个个装饰类,在需要的时候给对象穿上这个装饰。
1:
这张类图照这个样子发展下去不得了,子类无限膨胀,后面需求谁都不知道。这是我们一般扩展对象的正常方法,我们来看一下装饰者模式的原型。
2:
将需要扩展的功能独立起来,当需要的时候动态的添加功能。我想这就是装饰者名称由来,将后期扩展的功能比喻成装饰者,是很形象。
但是当我们带着这张图的原理去看代码的时候,它的结构根本不是这样的“干净”。所以说理论与实践是分不开的。请看代码:
using System; using System.Collections.Generic; using System.Text; namespace ConsoleApplication2 { public class ConcreteConpontent { public virtual void Operation() { Console.WriteLine("顶级待装饰对象"); } public virtual void Message() { Console.WriteLine("顶级对象消息"); } } public abstract class Decorator : ConcreteConpontent { protected ConcreteConpontent m_compontent; public void SetCompontent(ConcreteConpontent com) { m_compontent = com; } } public class ConcreteDecoratorA : Decorator { public override void Operation() { m_compontent.Operation(); Console.WriteLine("ConcreteDecoratorA进行了方法的动态添加"); } public override void Message() { m_compontent.Message(); Console.WriteLine("ConcreteDecoratorA进行了Message方法的动态添加"); } } public class ConcreteDecoratorB : Decorator { public override void Operation() { m_compontent.Operation(); Console.WriteLine("ConcreteDecoratorB进行了方法的装饰"); } public override void Message() { m_compontent.Message(); Console.WriteLine("ConcreteDecoratorB进行了Message方法的动态添加"); } } public class ConcreteDecoratorC : Decorator { public override void Operation() { m_compontent.Operation(); Console.WriteLine("ConcreteDecoratorC进行了方法的装饰"); } public override void Message() { m_compontent.Message(); Console.WriteLine("ConcreteDecoratorC进行了Message方法的动态添加"); } } }
装饰者模式的基本代码原型差不多就这样子的。当我看到装饰者模式是这样的一个代码结构的时候,其实说心里话我难受。里面不是带着继承吗?为什么要继承,心理面不忍发了点牢骚。ConcreteConpontent是被装饰者对象,首先我们要确定要扩展的对象是可以让我们扩展的。其实我知道继承是为了拿到要扩展对象的行为,并且标示所有的装饰者是属于一种类型的,在使用的时候就可以用基类来使用所有的装饰者。如果没有继承显然是不能用基类进行统一调用的,继承还有一个作用就是为了拿到被装饰者的行为,用它的为操作不同的实例,是够聪明的。
3:
我假如我不需要用基类进行统一调用装饰者,我是否就可以不继承自被装饰者了;为了能够实现装饰者的无限递增的装饰,我对代码进行了简单的修改,请看代码:
using System; namespace ConsoleApplication1 { public class ConcreteConpontent { public virtual void Operation() { Console.WriteLine("顶级待装饰对象"); } public virtual void message() { Console.WriteLine("顶级对象消息"); } } public abstract class Decorator { private ConcreteConpontent m_compontent; protected Decorator decorator; public void SetCompontent(ConcreteConpontent com, Decorator de) { m_compontent = com; decorator = de; } public void SetCompontent(ConcreteConpontent com) { m_compontent = com; } public virtual void Operation() { if (decorator != null) decorator.Operation(); else m_compontent.Operation(); } public virtual void message() { if (decorator != null) decorator.message(); else m_compontent.message(); } } public class ConcreteDecoratorA : Decorator { public override void Operation() { base.Operation(); Console.WriteLine("ConcreteDecoratorA进行了方法的装饰"); } public override void message() { base.message(); Console.WriteLine("ConcreteDecoratorA进行了message方法的动态添加"); } } public class ConcreteDecoratorB : Decorator { public override void Operation() { base.Operation(); Console.WriteLine("ConcreteDecoratorB进行了方法的装饰"); } public override void message() { base.message(); Console.WriteLine("ConcreteDecoratorB进行了message方法的动态添加"); } } public class ConcreteDecoratorC : Decorator { public override void Operation() { base.Operation(); Console.WriteLine("ConcreteDecoratorC进行了方法的装饰"); } public override void message() { base.message(); Console.WriteLine("ConcreteDecoratorC进行了message方法的动态添加"); } } }
如果我们这是想扩展一个简单的小功能,让我们继承一个很大的对象是不是有点不划算。只是想用被装饰者的行为,去操作装饰者原型实例。我们可以牺牲一下代码的冗余来解决这个性能问题。书上对继承的解释是用来避免手动输入被装饰者的行为代码。我觉得这点根本没有说服力。不继承我一样可以有同样的行为、一样可以实现无限递增的嵌套装饰者实例。要想实例套实例,那么他们必须来自同一个祖先,同样是装饰者,要想让装饰者套装饰者,那么在装饰者的类中需要有一个对装饰者类型的引用,但是每一个装饰者不可能一样。所以必须让他们继承同一个基类才行,后面再多的装饰者只要继承同一个基类那么就可以互相引用。[王清培版权所有,转载请给出署名]
总结:在我们选择使用装饰者模式的时候,需要根据自己的使用情况进行适当修改。在没有必要的情况下不需要继承那么大的一个对象。