装饰者模式其实有点难以理解,特别是对初学者来说可能有点晕,因为它的概念互相冲突,哪里互相冲突我们下面会讲解到。
本人保持一贯的写作风格,重在入门。在本人的这篇文章中会用一个比较恰当的比喻来让我们对问题迎刃而解,例子虽然简单但是重点突出。
在写这篇文章之前我在网上大概搜了一下关于“装饰者模式”的一些文章,但是讲解的效果都不太理想。要么就是找书搬过来的,要么就是对着书的例子从新创造一个。我看了大概三四篇这样子,不行看着头晕。文章的主人很想把问题的关键说清楚,但是很少能在原有代码的基础上画龙点睛,搞不好就是画蛇添足。如果没能清楚的介绍模式代码中的任何一脚,都有可能給看文章的初学者带来新的问题,没能够透彻的体现出文章的重点。下面我们从理清头绪开始。[王清培版权所有,转载请给出署名]
设计模式是用来解决某一个问题的一个方法,一个模式是对应着一个问题,比如观察者模式就是用来解决一对多的关系,这种关系是“牵一发而动全身”的作用。
我们所看的设计模式书籍是一系列问题的集合,也是设计模式的集合。在我们还没有能力将他们融会贯通之前,先单独理解这些思想。当我们能驾驭这些设计模式之后,我们就能够设计出不错的系统架构。模式之间是相通的,“设计原则”是引导模式创新的根本。书上的模式多数都是用来考虑一些小例子而已,如果用在真正的项目中,就需要结合整个设计模式的运用了。所以当我们学习一些小的设计模式时,我们不牵扯到其他的多余东西,先理解我们当前模式的真正的思想是什么。
装饰者模式定义:动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案;
这是装饰者模式比较官方的定义,这句话我们基本上都能理解其含义是什么。无非是动态的给要扩展的对象加上功能,不用继承而是用组合的方式。就是这句话给我们初学者带来了第一个问题,是用组合而不是用继承来扩展对象的功能。我们第一次接触装饰者模式的时候,就盯住了这句话,就是因为我们盯住了这句话,所以我们在下面的思考过程中总是带着这个理论,所以总是会理解不了。朋友先不要记这个理论,先抛开不要记任何理论模型,我会用一个比喻来逐渐的让你理解装饰者模式真正的含义是什么。
请进入我的学习模式,在这里我打一个比喻;假如我家里现在要装修,要装修一个天花板上的灯。大家都知道天花板上的灯都是需要灯具进行装饰的,在这里我们已经引入到了装饰的概念了,好我们再来分析问题。那么灯具里面的灯泡是不变的,我们又引入了以关键的概念,就是被装饰对象。灯泡是我们一装修的时候就有的,外面的灯具是随时可以更换的,这里就形成了典型的装饰者模式的原型。请看图:[王清培版权所有,转载请给出署名]
1:
里面的灯泡就是被装饰者,外面的灯具就是装饰者。我们已基本认识了装饰者模式的含义是什么了,下面我们就用代码来进行模拟装饰者模式。
灯泡代码:
02 |
using System.Collections.Generic; |
04 |
namespace ConsoleApplication1 |
不要问为什么灯泡类不是抽象的,在这里我们不讨论其他的原理,我们先理清装饰者模式在说,后面我会慢慢引入你们所迷惑的概念。
这是灯泡代码,里面很简单,一个表示灯泡型号的属性和一个点亮灯泡的方法。下面我们来看装饰灯泡的灯具代码。
矩形灯具代码:
02 |
using System.Collections.Generic; |
05 |
namespace ConsoleApplication1 |
12 |
private 灯泡 dengpaoobject; |
14 |
/// 既然是灯具装饰,那么必须能容纳灯泡,所以灯具必须引用灯泡 |
16 |
/// <param name="d">灯泡的实例</param> |
17 |
public void 添加装饰的灯泡(灯泡 d) |
24 |
/// <returns>添加装饰之后的效果</returns> |
25 |
public string 打开矩形灯具的效果() |
27 |
return dengpaoobject.点亮灯泡() + ",矩形灯具所发出的光" ; |
多边形灯具代码:
02 |
using System.Collections.Generic; |
05 |
namespace ConsoleApplication1 |
12 |
private 灯泡 dengpaoobject; |
14 |
/// 既然是灯具装饰,那么必须能容纳灯泡,所以灯具必须引用灯泡 |
16 |
/// <param name="d">灯泡的实例</param> |
17 |
public void 添加装饰的灯泡(灯泡 d) |
24 |
/// <returns>添加装饰之后的效果</returns> |
25 |
public string 打开多边形灯具的效果() |
27 |
return dengpaoobject.点亮灯泡() + ",多边形灯具所发出的光" ; |
矩形阴影灯具:
02 |
using System.Collections.Generic; |
05 |
namespace ConsoleApplication1 |
12 |
private 灯泡 dengpaoobject; |
14 |
/// 既然是灯具装饰,那么必须能容纳灯泡,所以灯具必须引用灯泡 |
16 |
/// <param name="d">灯泡的实例</param> |
17 |
public void 添加装饰的灯泡(灯泡 d) |
24 |
/// <returns>添加装饰之后的效果</returns> |
25 |
public string 打开多边形灯具的效果() |
27 |
return dengpaoobject.点亮灯泡() + ",矩形阴影灯具所发出的光" ; |
不要问为什么装饰灯具没有继承上面的灯泡,我之所以这样讲解,就是为了绕开那个给我们带来理解上困惑的陷阱。继续往下看就能彻底明白了。
装饰者跟被装饰者之间没有任何继承关系,“装饰者模式”官方的解释就是这个意思,只是通过组合来扩展对象的职责。如果正常情况下,我们肯定是用继承来解决灯具的装饰问题,有多少了灯具都继承自灯泡类,但是通过这种包含的方式就绕开了继承带来的耦合问题。上面的代码还远远没有完成,我们来看后面会出现什么样的问题。
模式灯具的效果:
02 |
using System.Collections.Generic; |
05 |
namespace ConsoleApplication1 |
09 |
static void Main( string [] args) |
11 |
灯泡 dengpao = new 灯泡(); |
13 |
矩形灯具 juxingdengju = new 矩形灯具(); |
14 |
juxingdengju.添加装饰的灯泡(dengpao); |
15 |
Console.WriteLine(juxingdengju.打开矩形灯具的效果()); |
18 |
多边形灯具 duobianxingdengju = new 多边形灯具(); |
19 |
duobianxingdengju.添加装饰的灯泡(dengpao); |
20 |
Console.WriteLine(duobianxingdengju.打开多边形灯具的效果()); |
23 |
矩形阴影灯具 yinyingdengju = new 矩形阴影灯具(); |
24 |
yinyingdengju.添加装饰的灯泡(dengpao); |
25 |
Console.WriteLine(yinyingdengju.打开多边形灯具的效果()); |
2:
从上面的灯具例子来看,我们基本上完成了装饰者模式的模拟。但是这样的代码扩展性太差,设计模式所提倡面向接口编程,而我们这里似乎没有看见接口。例子中的所有代码都是直接使用对象的名称,这样肯定是耦合的。我们继续延伸问题,来解决你刚开始开文章的困境,抽象类、接口、继承跑哪去了。总觉得例子的代码太简单了,哪像是设计模式啊。别急我们继续讨论,设计模式本来就是思想性的理论,需要耐心的研究。
我们的灯泡是实体类而不是抽象类,这就说明我们的灯泡设计是不合理的,我们假如灯泡是个半成品,这个半成品是不能直接用的,任何装饰者都需要经过一定的修改才能使用。所以这样一来,我们的灯泡类就是抽象的了;
那为什么需要用装饰者来继承被装饰者呢,其实很简单原因就是没有统一的接口。我们假如灯泡只能用一种方式打开,任何灯具都不能擅自修改这统一的接口。因为这接口是灯泡厂商规定的,比如一些电流、电压、线路原理等等都是不允许直接修改的。只有继承自装饰者对象我才能够使用被装饰者的约定,比如一个方法的名称、一个属性的名称。而我们在使用的时候完全是使用被装饰者的方式在使用,这样就破快了约定。
还有就是为什么我们没有用接口,我们来延伸出接口的使用。假如一个灯具可以装饰很多种灯泡,那么必然就需要一个统一的接口来约束这些必备条件。 比如灯具只能装饰多大的灯泡、什么型号的灯泡等等;
上面的几个问题,我就大概的描述了一下。下面我们来对代码进行一些修改,让它看起来像“装饰者模式”。
更改后的灯泡代码:
1 |
using System;<br> using System.Collections.Generic;<br> using System.Text; |
namespace ConsoleApplication1<br>{<br> public interface 灯泡系列<br> {<br> int 灯泡型号 { get ; }<br> string 打开灯();<br> }<br> public abstract class 灯泡 : 灯泡系列<br> {<br> public int 灯泡型号<br> {<br> get { return 10; }<br> }<br> public virtual string 打开灯()<br> {<br> return "灯泡已经点亮" ;<br> }<br> }<br> public class 红色灯泡 : 灯泡<br> {<br> public int 灯泡型号<br> {<br> get { return 10; }<br> }<br> public override string 打开灯()<br> {<br> return "红色灯泡" ;<br> }<br> }<br>} |
更改后的矩形灯具代码:
1 |
using System;<br> using System.Collections.Generic;<br> using System.Text; |
namespace ConsoleApplication1<br>{<br> public class 矩形灯具 : 灯泡<br> {<br> /// <summary><br> /// 灯泡的引用<br> /// </summary><br> private 灯泡系列 dengpaoobject;<br> /// <summary><br> /// 既然是灯具装饰,那么必须能容纳灯泡,所以灯具必须引用灯泡<br> /// </summary><br> /// <param name="d">灯泡的实例</param><br> public void 添加装饰的灯泡(灯泡 d)<br> {<br> dengpaoobject = d;<br> }<br> public override string 打开灯()<br> {<br> return dengpaoobject.打开灯() + ",外加矩形灯具效果";<br> }<br> }<br>} |
其他几个灯具代码都一样的我就不贴出来了。
调用代码:
using System.Collections.Generic; |
namespace ConsoleApplication1 |
static void Main( string [] args) |
红色灯泡 reddengpao = new 红色灯泡(); |
矩形灯具 juxingdengju = new 矩形灯具(); |
多边形灯具 duobianxingdengju = new 多边形灯具(); |
duobianxingdengju.添加装饰的灯泡(reddengpao); |
juxingdengju.添加装饰的灯泡(duobianxingdengju); |
Console.WriteLine(juxingdengju.打开灯()); |
3:
装饰者模式就讲的差不多了。这里面用到了接口、继承、多态等特性,其实接口就是用来消除类之间的耦合,继承是为了拿对象的行为,多态是为了将职责动态的添加到灯具中去。模式中的一些特性是会随着环境的不同而不同,有些时候根本不需要接口,但是为了进行多类型之间的扩展就必须进行接口编程。
总结:我个人觉得装饰者模式用的场合不是很多,继承就是为了拿对象的行为,然后在单独引用一个对象的实例,这样就等于浪费了一个对象的内存。简单的装饰者可以不用继承,如果需要统一调用的话就需要继承了,接口只是用来表示装饰者不仅仅可以装饰某一个对象,而是某一类对象。根据需要的不同模式可以适当的进行修改,以适应当前环境。