解读设计模式----装饰模式(Decorator Pattern)
装饰模式(Decorator)也叫包装器模式(Wrapper)。以“装饰”的含义生动形象地描绘了“动态地给一个对象添加一些额外的职责”的意图。GOF在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。装饰模式充分利用了继承和聚合的优势,创造出无与论比的设计美学。就增加功能来说,Decorator模式相比生成子类更为灵活。
UML图:
一、使用Decorator模式的动机
现在有这样一个场景,要求我们为一个对象动态添加新的职责,这个职责并不修改原有的行为,而是在原有行为的基础上添加新的功能,就好比我们在吃薯条的时候涂上新鲜美味的番茄汁一般。
从面向对象的角度来说,我们要为一个对象添加一新的职责完全可以利用继承机制来实现,但是这样的设计会导致一个问题,“过度地使用继承来扩展对象的功能”由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着字类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀,也就是所谓的类爆炸。如何使“对象功能的扩展”能够根据需要来动态地实现同时又避免“扩展功能的增加”带来的类爆炸问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
二、迎接Decorator的到来
例如我们要为一只“笔”来设计其行为。从我们拥有的面向对象的知识出发,为一个对象的原有方法添加新的职责,可以通过继承机制,从写基类方法来实现,此时父类方法应为虚方法或是抽象方法。
如果我们需要为该笔添加新的职责,让其可以调整字体大小(这样的示例好象有点不符合现实哈,此处只是通过这个虚有的对象来演示Decorator,暂不考虑现实问题),则可以定义一子类继承于Pen,然后重写Write方法。
以上的实际完全符合装饰模式的意图“不改变原有的行为动态地给一个对象添加一些新的功能”。想想,这样的设计美好吗?现在需求改变,要求这种笔出了能写之外同时还拥有可以设置字体大小及字体颜色的设置功能,按继承机制的思想来设计是不是应该继承BlodPen然后又重写Write方法呢?这样下去就形成了一个多子类的延伸的多重继承体系,最终出现的问题就是类无限的增多,既所谓的类爆炸。
三、重构Decorator的设计
要解决上述出现类爆炸的问题该怎么办呢?仔细观察就会发现,通过继承子类在添加新的职责的时候都需要重写Write方法才能实现,那我们是不是应该重构一下,抽象出共性呢?答案是肯定的,我们完全可以把Write方法抽象为接口或抽象类,通过抽象后我们可以通过聚合的方法动态的组合新的功能职责。重构后的设计如下:
接口的抽象:
Pen类的设计:
BoldPen类的设计:
通过共性的抽象和一系列的新功能职责的装饰,新的设计应运而生,现在如果我们需要扩展出一种新的笔种,要求出了具备基本的写行为之外,还同时带有可设置字体大小和字体颜色的功能。显然,我们之前的设计就派上了用场,这时我们只需要通过聚合的方法就能够组合成这一种新的角色(笔种)出来。很明显应用了Decorator让设计变得更加灵活,同时也说明了一个问题,在我们实际的设计中,应少用继承多,尽量的通过聚合的方法来设计,提高设计灵活度。
通过组合设计出的新的笔种:
四、完善Decorator的设计
此部分我就不多说了,代码也已经在上面展示出。示例运行结果:
五、Decorator模式的要点
通过采用组合,而非继承手法,Decorator模式实现了在运行时动态地扩展对象功能的行为,而且可以根据需要扩展多个功能,避免了单独使用继承所带来“灵活性差”和“类爆炸”等问题。把不同的职责封装在不同的职责类的私有方法或属性中,这样对内开放,对外封闭。符合面向对象的“单一职责”和“开放--封闭”原则;同时也很好的符合面向对象设计原则中的“优先使用对象组合而非继承”。
六、本文参考资料
张逸 《软件设计精要与模式》 电子工业出版社
MSDN WebCast 《C#面向对象设计模式纵横谈(10) Decorator装饰模式(结构型模式)》
----------------------------------------------------------------------------------------------------------
UML图:
一、使用Decorator模式的动机
现在有这样一个场景,要求我们为一个对象动态添加新的职责,这个职责并不修改原有的行为,而是在原有行为的基础上添加新的功能,就好比我们在吃薯条的时候涂上新鲜美味的番茄汁一般。
从面向对象的角度来说,我们要为一个对象添加一新的职责完全可以利用继承机制来实现,但是这样的设计会导致一个问题,“过度地使用继承来扩展对象的功能”由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着字类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀,也就是所谓的类爆炸。如何使“对象功能的扩展”能够根据需要来动态地实现同时又避免“扩展功能的增加”带来的类爆炸问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
二、迎接Decorator的到来
例如我们要为一只“笔”来设计其行为。从我们拥有的面向对象的知识出发,为一个对象的原有方法添加新的职责,可以通过继承机制,从写基类方法来实现,此时父类方法应为虚方法或是抽象方法。
1namespace DesignPattern.Decorator
2{
3 public class Pen
4 {
5 public virtual string Write()
6 {
7 return "普通的笔"; //只能进行最基本的写操作
8 }
9 }
10}
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}
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
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
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方法,通过特定的属性或方法(我们可以叫它装饰方法)把自己的职责封装在自己的内部,这样的设计也完全符合面向对象的单一职责设计思想。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}
通过共性的抽象和一系列的新功能职责的装饰,新的设计应运而生,现在如果我们需要扩展出一种新的笔种,要求出了具备基本的写行为之外,还同时带有可设置字体大小和字体颜色的功能。显然,我们之前的设计就派上了用场,这时我们只需要通过聚合的方法就能够组合成这一种新的角色(笔种)出来。很明显应用了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 //同样我们还可为其添加自己的职责
26 }
27 }
28}
29
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 //同样我们还可为其添加自己的职责
26 }
27 }
28}
29
四、完善Decorator的设计
此部分我就不多说了,代码也已经在上面展示出。示例运行结果:
五、Decorator模式的要点
通过采用组合,而非继承手法,Decorator模式实现了在运行时动态地扩展对象功能的行为,而且可以根据需要扩展多个功能,避免了单独使用继承所带来“灵活性差”和“类爆炸”等问题。把不同的职责封装在不同的职责类的私有方法或属性中,这样对内开放,对外封闭。符合面向对象的“单一职责”和“开放--封闭”原则;同时也很好的符合面向对象设计原则中的“优先使用对象组合而非继承”。
六、本文参考资料
张逸 《软件设计精要与模式》 电子工业出版社
MSDN WebCast 《C#面向对象设计模式纵横谈(10) Decorator装饰模式(结构型模式)》
----------------------------------------------------------------------------------------------------------