一、装饰(Decorator)模式
装饰(Decorator)模式又称包装(Wrapper)模式[GOF95]。装饰模式以透明的方式扩展对象的功能,是继承关系的替代方案。
引言
孙悟空有72个变化,他的每一个变化都给他带来了额外的技能。当他成为一条鱼时,他可以在水里游泳;当他成为一只雀时,他可以在天空中飞行。无论悟空如何变化,在二郎的眼中,他永远是猴子。
装饰模式动态地以透明的方式给客户增加更多的责任。换句话说,客户端不会觉得装饰前后的对象有什么不同。装饰模式可以扩展对象的功能,而不需要创建更多的子类。
二、装饰模式的结构装饰模式采用原装饰类的子类实例,将客户端调用分配给装饰类。装饰模式的关键是这种扩展是完全透明的。
在孙悟空的例子中,老孙变成的鱼相当于老孙的子类。这条鱼与外界的互动应该通过“分配”给老孙的本尊,老孙的本尊应该采取行动。
装饰模式类图如下图所示:
装饰模式中的每个角色都有:
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接受额外责任的对象。
- 具体构件(Concrete Component)角色:定义一个将接受额外责任的类别。
- 装饰(Decorator)角色:持有构件(Component)对象的实例,并定义与抽象构件接口一致的接口。
- 具体装饰(Concrete Decorator)角色:对构件对象“附加”负责。
以下示例代码实现了装饰模式:
// Decorator pattern -- Structural example using System;// "Component"abstract class Component{ // Methods abstract public void Operation();}// "ConcreteComponent"class ConcreteComponent : Component{ // Methods override public void Operation() { Console.WriteLine("ConcreteComponent.Operation()"); }}// "Decorator"abstract class Decorator : Component{ // Fields protected Component component; // Methods public void SetComponent( Component component ) { this.component = component; } override public void Operation() { if( component != null ) component.Operation(); }}// "ConcreteDecoratorA"class ConcreteDecoratorA : Decorator{ // Fields private string addedState; // Methods override public void Operation() { base.Operation(); addedState = "new state"; Console.WriteLine("ConcreteDecoratorA.Operation()"); }}// "ConcreteDecoratorB"class ConcreteDecoratorB : Decorator{ // Methods override public void Operation() { base.Operation(); AddedBehavior(); Console.WriteLine("ConcreteDecoratorB.Operation()"); } void AddedBehavior() { }}///////////// <summary>/// Client test/// </summary>public class Client{ public static void Main( string[] args ) { // Create ConcreteComponent and two Decorators ConcreteComponent c = new ConcreteComponent(); ConcreteDecoratorA d1 = new ConcreteDecoratorA(); ConcreteDecoratorB d2 = new ConcreteDecoratorB(); // Link decorators d1.SetComponent( c ); d2.SetComponent( d1 ); d2.Operation(); }}上述代码在执行装饰时是通过Setcomponent实现的,在实际应用中,也有通过构造函数实现的,一个典型的创建过程可能如下:new Decorator( new Decorator2( new Decorator3( new ConcreteComponent() ) ) )
装饰模式通常被称为包装模式,因为每个特定的装饰类别都包裹着下一个特定的装饰类别或特定的组件类别。
四、装修模式应该在什么情况下使用?装饰模式应在以下情况下使用:
- 需要扩展一个类的功能,或者增加一个类的额外责任。
- 需要动态地向对象添加功能,这些功能可以动态地撤销。
- 由于某些基本功能的排列组合,需要增加大量的功能,从而使继承关系变得不现实。
这个例子展示了图书馆的图书和视频带通过装饰模式添加的“借阅”装饰。
// Decorator pattern -- Real World example using System;using System.Collections;// "Component"abstract class LibraryItem{ // Fields private int numCopies; // Properties public int NumCopies { get{ return numCopies; } set{ numCopies = value; } } // Methods public abstract void Display();}// "ConcreteComponent"class Book : LibraryItem{ // Fields private string author; private string title; // Constructors public Book(string author,string title,int numCopies) { this.author = author; this.title = title; this.NumCopies = numCopies; } // Methods public override void Display() { Console.WriteLine( " Book ------ " ); Console.WriteLine( " Author: {0}", author ); Console.WriteLine( " Title: {0}", title ); Console.WriteLine( " # Copies: {0}", NumCopies ); }}// "ConcreteComponent"class Video : LibraryItem{ // Fields private string director; private string title; private int playTime; // Constructor public Video( string director, string title, int numCopies, int playTime ) { this.director = director; this.title = title; this.NumCopies = numCopies; this.playTime = playTime; } // Methods public override void Display() { Console.WriteLine( " Video ----- " ); Console.WriteLine( " Director: {0}", director ); Console.WriteLine( " Title: {0}", title ); Console.WriteLine( " # Copies: {0}", NumCopies ); Console.WriteLine( " Playtime: {0}", playTime ); }}// "Decorator"abstract class Decorator : LibraryItem{ // Fields protected LibraryItem libraryItem; // Constructors public Decorator ( LibraryItem libraryItem ) { this.libraryItem = libraryItem; } // Methods public override void Display() { libraryItem.Display(); }}// "ConcreteDecorator"class Borrowable : Decorator{ // Fields protected ArrayList borrowers = new ArrayList(); // Constructors public Borrowable( LibraryItem libraryItem ) : base( libraryItem ) {} // Methods public void BorrowItem( string name ) { borrowers.Add( name ); libraryItem.NumCopies--; } public void ReturnItem( string name ) { borrowers.Remove( name ); libraryItem.NumCopies++; } public override void Display() { base.Display(); foreach( string borrower in borrowers ) Console.WriteLine( " borrower: {0}", borrower ); }} ///////////// <summary>/// DecoratorApp test/// </summary>public class DecoratorApp{ public static void Main( string[] args ) { // Create book and video and display Book book = new Book( "Schnell", "My Home", 10 ); Video video = new Video( "Spielberg", "Schindler's list", 23, 60 ); book.Display(); video.Display(); // Make video borrowable, then borrow and display Console.WriteLine( " Video made borrowable:" ); Borrowable borrowvideo = new Borrowable( video ); borrowvideo.BorrowItem( "Cindy Lopez" ); borrowvideo.BorrowItem( "Samuel King" ); borrowvideo.Display(); }}
六、利用装饰模式的优缺点
装饰模式的使用主要有以下优点:
- 装饰模式与继承关系的目的是扩大对象的功能,但装饰模式可以提供比继承更灵活的灵活性。
- 设计师可以通过使用不同的具体装饰和这些装饰的排列组合来创造许多不同行为的组合。
- 这一特点比继承更灵活,也意味着装饰模式比继承更容易出错。
装饰模式的使用主要有以下缺点:
由于装饰模式的使用,比继承关系的使用需要更少的类别。使用较少的类别,当然,使设计更容易进行。但另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使检查错误变得困难,特别是它们看起来非常相似。
七、讨论模式实现在大多数情况下,装饰模式的实现比上述定义中给出的示意实现更简单。在简化模式时,应注意以下情况:
(1)装饰接口必须与装饰接口兼容。
(2)尽量把Component作为“轻”类,不要把太多的逻辑和状态放在Component类中。
(3)如果只有一个ConcreteComponent类,没有抽象的Component类(接口),那么Decorator类往往可以是ConcreteComponent的子类。如下图所示:
(4)如果只有一个Concretedecorator类,就没有必要建立一个单独的Decorator类,而是可以将Decorator和Concretedecorator类的责任合并为一个类。
八、透明度要求透明的装饰模式
装饰模式通常需要抽象编程。装饰模式对客户端透明度的要求程序不应声明Concretedecorator类型的变量,而应声明Component类型的变量。换句话说,以下做法是正确的:
Componentc=newConcreteComponent();
componentc1=newconcretedecorator(c);
Componentc222=newConcreteDecorator(c1);
以下做法是错误的:
ConcreteComponentc=newConcreteDecorator();
这就是上面提到的,装饰模式对客户端是完全透明的。
以孙悟空为例,我们必须始终把孙悟空的所有变化都当作孙悟空来对待。如果我们把孙子变成的雀当作雀,而不是孙子,我们就会被孙子欺骗,这不应该发生。
以下做法是错误的:
大圣本尊c=new大圣本尊();
雀儿bird=new雀儿(c);
半透明装饰模式
然而,很难找到纯粹的装饰模式。装饰模式的目的是在不改变界面的情况下提高所考虑的类别的性能。在提高性能时,通常需要建立一种新的开放方法。即使在孙的系统中,也需要一种新的方法。例如,齐天圣人没有飞行的能力,而雀有。这意味着雀应该有一种新的fly()方法。
这导致大多数装饰模式的实现是“半透明”(semi-transparent)是的,而不是完全“透明”。换句话说,允许装饰模式改变接口并添加新方法。也就是说,声明Concretedecorator类型的变量,以便调用Concretedecorator类型中的方法:
齐天大圣c=new大圣本尊();
雀儿bird=new雀儿(c);
bird.fly();
齐天大圣界面根本没有fly()这种方法,雀儿界面也有这种方法。
九、装饰模式在.NET中的应用.net中有以下类型:
以下代码段用于输出XmlDocument的内容格式。我们可以理解Decorator模式在这里的作用。
// ConcreteComponent(内存流ms)生成MemoryStream ms = new MemoryStream();// XmltextWriter用于内存流 ms 装饰//////// XmlTextWriteriter采用半透明装饰模式 xtw = new XmlTextWriter(ms, Encoding.UTF8);xtw.Formatting = Formatting.Indented;// 装饰xtw的操作将转向操作本体-msxmlDoc内存流.Save(xtw);byte[] buf = ms.ToArray();txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);xtw.Close();
参考文献:阎宏,Java与模式,电子工业出版社[美]James W. Cooper,《C#电子工业出版社[美]设计模式Alan Shalloway James R. Trott,《Design Patterns Explained》,中国电力出版社[美]Robert C. Martin,敏捷软件开发-清华大学出版社[美]的原则、模式和实践Don Box, Chris Sells,《.NET本质论 第一卷:公共语言运行库,中国电力出版社http://www.dofactory.com/Patterns/Patterns.aspx