我是如何学习设计模式的六:装饰模式
模式四:装饰模式
理论
装饰模式(Decorator)也叫包装器模式(Wrapper)。以“装饰”的含义生动形象地描绘了“动态地给一个对象添加一些额外的职责”的意图。GOF在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。装饰模式充分利用了继承和聚合的优势,创造出无与论比的设计美学。就增加功能来说,Decorator模式相比生成子类更为灵活。
从面向对象的角度来说,我们要为一个对象添加一新的职责完全可以利用继承机制来实现,但是这样的设计会导致一个问题,“过度地使用继承来扩展对象的功能”由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着字类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀,也就是所谓的类爆炸。如何使“对象功能的扩展”能够根据需要来动态地实现同时又避免“扩展功能的增加”带来的类爆炸问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰模式的特点;
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的索引(reference)
(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
下表格列举了装饰模式和继承的不同:
装饰模式 VS 继承
装饰模式 继承
用来扩展特定对象的功能 用来扩展一类对象的功能
不需要子类 需要子类
动态地 静态地
运行时分配职责 编译时分派职责
防止由于子类而导致的复杂和混乱 导致很多子类产生,在一些场合,报漏类的层次
更多的灵活性 缺乏灵活性
对于一个给定的对象,同时可能有不同的装饰对象,客户端可以通过它的需要选择合适的装饰对象发送消息。 对于所有可能的联合,客户期望
很容易增加任何的 困难
结构图
生活中的例子
装饰模式动态地给一个对象添加额外的职责。不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
二、迎接Decorator的到来-例子1
例如我们要为一只“笔”来设计其行为。从我们拥有的面向对象的知识出发,为一个对象的原有方法添加新的职责,可以通过继承机制,从写基类方法来实现,此时父类方法应为虚方法或是抽象方法。
1namespace DesignPattern.Decorator
2{
3 public class Pen
4 {
5 public virtual string Write()
6 {
7 return "普通的笔"; //只能进行最基本的写操作
8 }
9 }
10}
如果我们需要为该笔添加新的职责,让其可以调整字体大小(这样的示例好象有点不符合现实哈,此处只是通过这个虚有的对象来演示Decorator,暂不考虑现实问题),则可以定义一子类继承于Pen,然后重写Write方法。
1namespace DesignPattern.Decorator
2{
3
4 public class BoldPen : Pen
5 {
6 public override string Write()
7 {
8 return base.Write() + FontSize();
9 }
10
11 private string FontSize()
12 {
13 return "字体大小:10px";
14 }
15 }
16} 以上的实际完全符合装饰模式的意图“不改变原有的行为动态地给一个对象添加一些新的功能”。想想,这样的设计美好吗?现在需求改变,要求这种笔出了能写之外同时还拥有可以设置字体大小及字体颜色的设置功能,按继承机制的思想来设计是不是应该继承BlodPen然后又重写Write方法呢?这样下去就形成了一个多子类的延伸的多重继承体系,最终出现的问题就是类无限的增多,既所谓的类爆炸。
三、重构Decorator的设计
要解决上述出现类爆炸的问题该怎么办呢?仔细观察就会发现,通过继承子类在添加新的职责的时候都需要重写Write方法才能实现,那我们是不是应该重构一下,抽象出共性呢?答案是肯定的,我们完全可以把Write方法抽象为接口或抽象类,通过抽象后我们可以通过聚合的方法动态的组合新的功能职责。重构后的设计如下:
接口的抽象:
1namespace DesignPattern.Decorator
2{
3 public interface IWrite
4 {
5 string Write();
6 }
7}
8
Pen类的设计:
1namespace DesignPattern.Decorator
2{
3 /**//// <summary>
4 /// 实现IWrite接口
5 /// </summary>
6 public class Pen:IWrite
7 {
8 public string Write()
9 {
10 return "能写的笔";
11 }
12 }
13}
14
BoldPen类的设计:1namespace DesignPattern.Decorator
2{
3 public class BoldPen:IWrite
4 {
5 IWrite component = null;
6 int borderWidth = 0;
7
8 public BoldPen() { }
9
10 public BoldPen(IWrite compontent, int borderWidth)
11 {
12 this.component = compontent;
13 this.borderWidth = borderWidth;
14 }
15
16 /**//// <summary>
17 /// 接口方法
18 /// </summary>
19 public string Write()
20 {
21 //调用接口方法
22 string pen = this.component.Write();
23 return pen + " 字体大小:" + this.borderWidth.ToString();
24 }
25
26 /**//// <summary>
27 /// 装饰方法
28 /// </summary>
29 /// <param name="borderWidth"></param>
30 public void SetBorderWidth(int borderWidth)
31 {
32 this.borderWidth = borderWidth;
33 }
34 }
35}
如上的设计通过抽象出接口方法Write,不同的功能组件去继承实现IWrite接口实现Write方法,通过特定的属性或方法(我们可以叫它装饰方法)把自己的职责封装在自己的内部,这样的设计也完全符合面向对象的单一职责设计思想。
通过共性的抽象和一系列的新功能职责的装饰,新的设计应运而生,现在如果我们需要扩展出一种新的笔种,要求出了具备基本的写行为之外,还同时带有可设置字体大小和字体颜色的功能。显然,我们之前的设计就派上了用场,这时我们只需要通过聚合的方法就能够组合成这一种新的角色(笔种)出来。很明显应用了Decorator让设计变得更加灵活,同时也说明了一个问题,在我们实际的设计中,应少用继承多,尽量的通过聚合的方法来设计,提高设计灵活度。
通过组合设计出的新的笔种:
1namespace DesignPattern.Decorator
2{
3 public class BoldColorPen:IWrite
4 {
5 private List<IWrite> list=null;
6 public BoldColorPen()
7 { }
8
9 public BoldColorPen(List<IWrite> list)
10 {
11 this.list = list;
12 }
13 public string Write()
14 {
15 string str = string.Empty;
16 for (int i = 0; i < list.Count; i++)
17 {
18 str += ((IWrite)list[i]).Write() + " ";
19 }
20 return str;
21 }
22
23 private string Other()
24 {
25 //同样我们还可为其添加自己的职责