学习设计模式第十二 - 装饰(者)模式

本文摘取自TerryLee(李会军)老师的设计模式系列文章,版权归TerryLee,仅供个人学习参考。转载请标明原作者TerryLee。部分示例代码来自DoFactory

 

概述

在软件系统中,有时候我们会使用继承来扩展对象的功能,但是由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。如何使"对象功能的扩展"能够根据需要来动态地实现?同时避免"扩展功能的增多"带来的子类膨胀问题?从而使得任何"功能扩展变化"所导致的影响将为最低?这就是本文要讲的Decorator模式。

 

意图

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

 

UML

 

图1 Decorator模式结构图

 

参与者

这个模式涉及的类或对象:

  • Component

    • 定义一个接口,通过这个接口可以动态向自身添加包含其他职责的对象。

  • ConcreteComponent

    • 定义一个包含具体职责的对象,也可以给这个对象添加包含额外功能的对象。

  • Decorator

    • 继承自Component,并维护一个到Component对象的引用并定义一个符合Component接口的接口,通过这个接口来调用Component的接口方法。注意,Component无需知道Decorator的存在。

  • ConcreteDecorator

    • 具体的装饰对象,向component添加功能。

 

适用性

装饰模式的目的动态扩展对象的行为,即为已有功能动态地添加更多功能的一种方式。这种动态附加新行为到对象的能力是通过装饰类"包装"一个与其接口一致的原始类来实现的。

当系统需要新功能的时候,如果是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为。这样在主类中加入新的字段,新的方法和新的逻辑,会增加主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。装饰模式为这种情况提供了一个非常好的解决方案,其把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。这样,客户端就可以在运行时根据需要有选的,有顺序的使用装饰功能包装对象了。

装饰模式合并了多态与委托。客户端对包装类与原始的类一致的调用方式体现了多态性。在大多数情况下,方法调用传递给原始类,然后结果起作用,或是被装饰而添加了附加功能。装饰是一种灵活的技术,因为它发生在运行时,与之相反继承的作用发生在编译时。

在以下情况下应当使用装饰模式:

  1. 在不影响其他对象的情况下,扩展一个类的功能,或动态、透明的给一个类增加附加责任。

  2. 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实,即当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

  4. 需要把所需的功能按正确的顺序串联起来进行控制。

 

DoFactory GoF代码

这个例子演示了通过装饰模式向现有对象动态添加额外功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Decorator pattern 
// Structural example 
using System;
  
namespace DoFactory.GangOfFour.Decorator.Structural
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            // 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();
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Component"
    abstract class Component
    {
        public abstract void Operation();
    }
  
    // "ConcreteComponent"
    class ConcreteComponent : Component
    {
        public override void Operation()
        {
            Console.WriteLine("ConcreteComponent.Operation()");
        }
    }
  
    // "Decorator"
    abstract class Decorator : Component
    {
        protected Component component;
  
        public void SetComponent(Component component)
        {
            this.component = component;
        }
  
        public override void Operation()
        {
            if (component != null)
            {
                component.Operation();
            }
        }
    }
  
    // "ConcreteDecoratorA"
    class ConcreteDecoratorA : Decorator
    {
        public override void Operation()
        {
            base.Operation();
            Console.WriteLine("ConcreteDecoratorA.Operation()");
        }
    }
  
    // "ConcreteDecoratorB"
    class ConcreteDecoratorB : Decorator
    {
        public override void Operation()
        {
            base.Operation();
            AddedBehavior();
            Console.WriteLine("ConcreteDecoratorB.Operation()");
        }
  
        void AddedBehavior()
        {
        }
    }
}

这个例子演示了使用装饰模式将"借阅"功能添加到现存的物品(书籍与音响)。

例子中涉及到的类与桥接模式中标准的类对应关系如下:

  • Component - LibraryItem

  • ConcreteComponent - Book, Video

  • Decorator - Decorator

  • ConcreteDecorator - Borrowable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Decorator pattern 
// Real World example 
using System;
using System.Collections.Generic;
  
namespace DoFactory.GangOfFour.Decorator.RealWorld
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            // Create book
            Book book = new Book("Worley""Inside ASP.NET", 10);
            book.Display();
  
            // Create video
            Video video = new Video("Spielberg""Jaws", 23, 92);
            video.Display();
  
            // Make video borrowable, then borrow and display
            Console.WriteLine("\nMaking video borrowable:");
  
            Borrowable borrowvideo = new Borrowable(video);
            borrowvideo.BorrowItem("Customer #1");
            borrowvideo.BorrowItem("Customer #2");
  
            borrowvideo.Display();
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Component"
    abstract class LibraryItem
    {
        private int _numCopies;
  
        // Property
        public int NumCopies
        {
            get return _numCopies; }
            set { _numCopies = value; }
        }
  
        public abstract void Display();
    }
  
    // "ConcreteComponent"
    class Book : LibraryItem
    {
        private string _author;
        private string _title;
  
        // Constructor
        public Book(string author, string title, int numCopies)
        {
            this._author = author;
            this._title = title;
            this.NumCopies = numCopies;
        }
  
        public override void Display()
        {
            Console.WriteLine("\nBook ------ ");
            Console.WriteLine(" Author: {0}", _author);
            Console.WriteLine(" Title: {0}", _title);
            Console.WriteLine(" # Copies: {0}", NumCopies);
        }
    }
  
    // "ConcreteComponent"
    class Video : LibraryItem
    {
        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;
        }
  
        public override void Display()
        {
            Console.WriteLine("\nVideo ----- ");
            Console.WriteLine(" Director: {0}", _director);
            Console.WriteLine(" Title: {0}", _title);
            Console.WriteLine(" # Copies: {0}", NumCopies);
            Console.WriteLine(" Playtime: {0}\n", _playTime);
        }
    }
  
    // "Decorator"
    abstract class Decorator : LibraryItem
    {
        protected LibraryItem libraryItem;
  
        // Constructor
        public Decorator(LibraryItem libraryItem)
        {
            this.libraryItem = libraryItem;
        }
  
        public override void Display()
        {
            libraryItem.Display();
        }
    }
  
    // "ConcreteDecorator"
    class Borrowable : Decorator
    {
        protected List<string> borrowers = new List<string>();
  
        // Constructor
        public Borrowable(LibraryItem libraryItem)
            base(libraryItem)
        {
        }
  
        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: " + borrower);
            }
        }
    }
}

.NET优化代码实现了与上述示例相同的功能,主要演示了泛型在装饰模式中的作用。泛型类型使用确定的借阅物的类型取代了string,增强了类型安全。另外在LibraryItem抽象类型中引入了自动属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Decorator Design Pattern
// .NET optimized example
using System;
using System.Collections.Generic;
  
namespace DoFactory.GangOfFour.Decorator.NETOptimized
{
    class MainApp
    {
        static void Main()
        {
            // Create book
            var book = new Book("Worley""Inside ASP.NET", 10);
            book.Display();
  
            // Create video
            var video = new Video("Spielberg""Jaws", 23, 92);
            video.Display();
  
            // Make video borrowable, then borrow and display
            Console.WriteLine("\nMaking video borrowable:");
  
            var borrow = new Borrowable<Video>(video);
            borrow.BorrowItem("Customer #1");
            borrow.BorrowItem("Customer #2");
  
            borrow.Display();
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    // "Component"
    abstract class LibraryItem<T>
    {
        // Each T has its own NumCopies
        public static int NumCopies { getset; }
  
        public abstract void Display();
    }
  
    // "ConcreteComponent"
    class Book : LibraryItem<Book>
    {
        private string _author;
        private string _title;
  
        // Constructor
        public Book(string author, string title, int numCopies)
        {
            this._author = author;
            this._title = title;
            NumCopies = numCopies;
        }
  
        public override void Display()
        {
            Console.WriteLine("\nBook ------ ");
            Console.WriteLine(" Author: {0}", _author);
            Console.WriteLine(" Title: {0}", _title);
            Console.WriteLine(" # Copies: {0}", NumCopies);
        }
    }
  
    // "ConcreteComponent"
    class Video : LibraryItem<Video>
    {
        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;
            NumCopies = numCopies;
            this._playTime = playTime;
        }
  
        public override void Display()
        {
            Console.WriteLine("\nVideo ----- ");
            Console.WriteLine(" Director: {0}", _director);
            Console.WriteLine(" Title: {0}", _title);
            Console.WriteLine(" # Copies: {0}", NumCopies);
            Console.WriteLine(" Playtime: {0}\n", _playTime);
        }
    }
  
    // "Decorator"
    abstract class Decorator<T> : LibraryItem<T>
    {
        private LibraryItem<T> _libraryItem;
  
        // Constructor
        public Decorator(LibraryItem<T> libraryItem)
        {
            _libraryItem = libraryItem;
        }
  
        public override void Display()
        {
            _libraryItem.Display();
        }
    }
  
    // "ConcreteDecorator"
    class Borrowable<T> : Decorator<T>
    {
        private List<string> _borrowers = new List<string>();
  
        // Constructor
        public Borrowable(LibraryItem<T> libraryItem)
            base(libraryItem)
        {
        }
  
        public void BorrowItem(string name)
        {
            _borrowers.Add(name);
            NumCopies--;
        }
  
        public void ReturnItem(string name)
        {
            _borrowers.Remove(name);
            NumCopies++;
        }
  
        public override void Display()
        {
            base.Display();
            _borrowers.ForEach(b =>  Console.WriteLine(" borrower: " + b));
        }
    }
}

 

来自《深入浅出设计模式》的例子

这个例子以配置不同组合咖啡的过程演示了装饰模式,牛奶、豆浆等辅料被作为装饰加入到(装饰)不同种类的咖啡中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System;
  
namespace DoFactory.HeadFirst.Decorator.Starbuzz
{
    class StarbuzzCoffee
    {
        static void Main(string[] args)
        {
            Beverage beverage = new Espresso();
  
            Console.WriteLine(beverage.Description
                " $" + beverage.Cost);
  
            Beverage beverage2 = new DarkRoast();
            beverage2 = new Mocha(beverage2);
            beverage2 = new Mocha(beverage2);
            beverage2 = new Whip(beverage2);
            Console.WriteLine(beverage2.Description
                " $" + beverage2.Cost);
  
            Beverage beverage3 = new HouseBlend();
            beverage3 = new Soy(beverage3);
            beverage3 = new Mocha(beverage3);
            beverage3 = new Whip(beverage3);
            Console.WriteLine(beverage3.Description
                " $" + beverage3.Cost);
  
            // Wait for user
            Console.ReadKey();
        }
    }
  
    #region Beverage
  
    public class Beverage
    {
        public virtual string Description { getprotected set; }
        public virtual double Cost { getprotected set; }
    }
  
    public class DarkRoast : Beverage
    {
        public DarkRoast()
        {
            Description = "Dark Roast Coffee";
            Cost = 0.99;
        }
    }
  
    public class Decaf : Beverage
    {
        public Decaf()
        {
            Description = "Decaf Coffee";
            Cost = 1.05;
        }
    }
  
    public class Espresso : Beverage
    {
        public Espresso()
        {
            Description = "Espresso";
            Cost = 1.99;
        }
    }
  
    public class HouseBlend : Beverage
    {
        public HouseBlend()
        {
            Description = "House Blend Coffee";
            Cost = 0.89;
        }
    }
  
    #endregion
  
    #region CondimentDecorator
  
    public abstract class CondimentDecorator : Beverage
    {
    }
  
    public class Whip : CondimentDecorator
    {
        private Beverage _beverage;
  
        public Whip(Beverage beverage)
        {
            this._beverage = beverage;
        }
  
        public override string Description
        {
            get return _beverage.Description + ", Whip"; }
        }
  
        public override double Cost
        {
            get return .10 + _beverage.Cost; }
        }
    }
  
    public class Milk : CondimentDecorator
    {
        private Beverage _beverage;
  
        public Milk(Beverage beverage)
        {
            this._beverage = beverage;
        }
  
        public override string Description
        {
            get return _beverage.Description + ", Milk"; }
        }
  
        public override double Cost
        {
            get return .10 + _beverage.Cost; }
        }
    }
  
    public class Mocha : CondimentDecorator
    {
        private Beverage _beverage;
  
        public Mocha(Beverage beverage)
        {
            this._beverage = beverage;
        }
  
        public override string Description
        {
            get return _beverage.Description + ", Mocha"; }
        }
  
        public override double Cost
        {
            get return .20 + _beverage.Cost; }
        }
    }
  
    public class Soy : CondimentDecorator
    {
        private Beverage _beverage;
  
        public Soy(Beverage beverage)
        {
            this._beverage = beverage;
        }
  
        public override string Description
        {
            get return _beverage.Description + ", Soy"; }
        }
  
        public override double Cost
        {
            get return .15 + _beverage.Cost; }
        }
    }
  
    #endregion
}

 

装饰模式解说

在软件开发中,经常会遇到动态地为一个对象而不是整个类增加一些功能的问题,还是以惯用的日志记录的例子来说明。现在要求我们开发的记录日志的组件,除了要支持数据库记录DatabaseLog和文本文件记录TextFileLog两种方式外,我们还需要在不同的应用环境中增加一些额外的功能,比如需要记录日志信息的错误严重级别,需要记录日志信息的优先级别,还有日志信息的扩展属性等功能。在这里,如果我们不去考虑设计模式,解决问题的方法其实很简单,可以通过继承机制去实现,日志类结构图如下:

图2.普通日志记录程序结构图

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Log
{
    public abstract void Write(string log);
}
  
public class DatabaseLog : Log
{
    public override void Write(string log)
    {
        //......记录到数据库中
    }
}
  
public class TextFileLog : Log
{
    public override void Write(string log)
    {
        //......记录到文本文件中
    }
}

需要记录日志信息的错误严重级别功能和记录日志信息优先级别的功能,只要在原来子类DatabaseLog和TextFileLog的基础上再生成子类即可,同时需要引进两个新的接口IError和IPriority,类结构图如下:

图3.增加记录错误严重级别及信息优先级的日志记录程序结构图

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public interface IError
{
    void SetError();
}
  
public interface IPriority
{
    void SetPriority();
}
  
public class DBErrorLog : DatabaseLog, IError
{
    public override void Write(string log)
    {
        base.Write(log);
    }
    public void SetError()
    {
        //......功能扩展,实现了记录错误严重级别
    }
}
  
public class DBPriorityLog : DatabaseLog, IPriority
{
    public override void Write(string log)
    {
        base.Write(log);
    }
    public void SetPriority()
    {
        //......功能扩展,实现了记录优先级别
    }
}
  
public class TFErrorLog : TextFileLog, IError
{
    public override void Write(string log)
    {
        base.Write(log);
    }
    public void SetError()
    {
        //......功能扩展,实现了记录错误严重级别
    }
}
  
public class TFPriorityLog : TextFileLog, IPriority
{
    public override void Write(string log)
    {
        base.Write(log);
    }
    public void SetPriority()
    {
        //......功能扩展,实现了记录优先级别
    }
}

此时可以看到,如果需要相应的功能,直接使用这些子类就可以了。这里我们采用了类的继承方式来解决了对象功能的扩展问题,这种方式是可以达到我们预期的目的。然而,它却带来了一系列的问题。首先,前面的分析只是进行了一种功能的扩展,如果既需要记录错误严重级别,又需要记录优先级时,子类就需要进行接口的多重继承,这在某些情况下会违反类的单一职责原则,注意下图中的蓝色区域:

图4.日志记录程序的设计违反单一职责原则

实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DBEPLog : DatabaseLog, IError, IPriority
{
    public override void Write(string log)
    {
        SetError();
        SetPriority();
        base.Write(log);
    }
    public void SetError()
    {
        //......功能扩展,实现了记录错误严重级别
    }
    public void SetPriority()
    {
        //......功能扩展,实现了记录优先级别
    }
}
  
public class TFEPLog : DatabaseLog, IError, IPriority
{
    public override void Write(string log)
    {
        SetError();
        SetPriority();
        base.Write(log);
    }
    public void SetError()
    {
        //......功能扩展,实现了记录错误严重级别
    }
    public void SetPriority()
    {
        //......功能扩展,实现了记录优先级别
    }
}

其次,随着以后扩展功能的增多,子类会迅速的膨胀,可以看到,子类的出现其实是DatabaseLog和TextFileLog两个子类与新增加的接口的一种排列组合关系,所以类结构会变得很复杂而难以维护,正如象李建忠老师说的那样"子类复子类,子类何其多";最后,这种方式的扩展是一种静态的扩展方式,并没有能够真正实现扩展功能的动态添加,客户程序不能选择添加扩展功能的方式和时机。

现在又该是Decorator模式出场的时候了,解决方案是把Log对象嵌入到另一个对象中,由这个对象来扩展功能。首先我们要定义一个抽象的包装类LogWrapper,让它继承于Log类,结构图如下:

图5.日志记录程序实现装饰模式第一步-增加包装类

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class LogWrapper : Log
{
    private Log _log;
    public LogWrapper(Log log)
    {
        _log = log;
    }
    public override void Write(string log)
    {
        _log.Write(log);
    }
}

现在对于每个扩展的功能,都增加一个包装类的子类,让它们来实现具体的扩展功能,如下图中绿色的区域:

6.继承包装类来实现扩展功能

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LogErrorWrapper : LogWrapper
{
    public LogErrorWrapper(Log _log)
        base(_log)
    {
  
    }
    public override void Write(string log)
    {
        SetError(); //......功能扩展
  
        base.Write(log);
    }
    public void SetError()
    {
        //......实现了记录错误严重级别
    }
}
  
public class LogPriorityWrapper : LogWrapper
{
    public LogPriorityWrapper(Log _log)
        base(_log)
    {
  
    }
    public override void Write(string log)
    {
        SetPriority(); //......功能扩展
  
        base.Write(log);
    }
    public void SetPriority()
    {
        //......实现了记录优先级别
    }
}

到这里,LogErrorWrapper类和LogPriorityWrapper类真正实现了对错误严重级别和优先级别的功能的扩展。我们来看一下客户程序如何去调用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Program
{
    public static void Main(string[] args)
    {
        Log log = new DatabaseLog();
        LogWrapper lew1 = new LogErrorWrapper(log);
        //扩展了记录错误严重级别
        lew1.Write("Log Message");
  
        LogPriorityWrapper lpw1 = new LogPriorityWrapper(log);
        //扩展了记录优先级别
        lpw1.Write("Log Message");
  
        LogWrapper lew2 = new LogErrorWrapper(log);
        LogPriorityWrapper lpw2 = new LogPriorityWrapper(lew2); //这里是lew2
        //同时扩展了错误严重级别和优先级别
        lpw2.Write("Log Message");
    }
}

注意在上面程序中的第三段装饰才真正体现出了Decorator模式的精妙所在,这里总共包装了两次:第一次对log对象进行错误严重级别的装饰,变成了lew2对象,第二次再对lew2对象进行装饰,于是变成了lpw2对象,此时的lpw2对象同时扩展了错误严重级别和优先级别的功能。也就是说我们需要哪些功能,就可以这样继续包装下去。到这里也许有人会说LogPriorityWrapper类的构造函数接收的是一个Log对象,为什么这里可以传入LogErrorWrapper对象呢?通过类结构图就能发现,LogErrorWrapper类其实也是Log类的一个子类。

我们分析一下这样会带来什么好处?首先对于扩展功能已经实现了真正的动态增加,只在需要某种功能的时候才进行包装;其次,如果再出现一种新的扩展功能,只需要增加一个对应的包装子类(注意:这一点任何时候都是避免不了的),而无需再进行很多子类的继承,不会出现子类的膨胀,同时Decorator模式也很好的符合了面向对象设计原则中的"优先使用对象组合而非继承"和"开放-封闭"原则。

总结一下装饰者模式的特点:

  • 装饰者和被装饰对象有相同的超类型。

  • 可以用一个或多个装饰者包装一个对象。

  • 装饰者和被装饰对象有相同的超类型,所以在任何使用被装饰对象的场合都可以使用装饰过的对象来代替。

  • 装饰者可以在被装饰的行为之前与/或之后,加上自己的行为,已达到特定目的。

  • 对象可以在任何时候被装饰,可以在运行时动态地、不限量的用喜欢的装饰者来装饰对象。

  • 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。

对装饰模式一个很经典的总结,通过继承来达到"类型匹配",而通过组合来获得行为,后者充分保证了灵活性。

 

.NET中的装饰模式

1. .NET中装饰模式一个典型的应用就是围绕Stream类设计的一系列类。Stream类是一个由IO设备(磁盘,socket,内存等)读写字节序列的抽象类。它的类结构如下:

图7. .NET中的Stream系列类使用了装饰模式

可以看到,BufferedStreamCryptoStream其实就是两个包装类,BufferedStream包装了Stream,在使用其读写大段字节时会提升性能,类似地,CryptoStream包装提供了在读写过程中加密与解密流的功能。BufferedStreamCryptoStream暴露了与Stream相同的接口方法ReadWriteSeekFlush等,而客户端并不知道它们之间的区别。装饰类通常有一个构造函数,其接受的参数表示这个Decorator要装饰的类,如new BufferedStream(Stream stream)。这里的Decorator模式省略了抽象装饰角色(Decorator)。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Program
{
    public static void Main(string[] args)
    {
        MemoryStream ms = new MemoryStream(new byte[] { 100, 456, 864, 222, 567 });
  
        //扩展了缓冲的功能
        BufferedStream buff = new BufferedStream(ms);
  
        //扩展了缓冲,加密的功能
        CryptoStream crypto = new CryptoStream(buff);
    }
}

通过反编译,可以看到BufferedStream类的代码(只列出部分),它是继承于Stream类:

1
2
3
4
5
6
7
8
9
10
public sealed class BufferedStream : Stream
{
    // Methods
    private BufferedStream();
    public BufferedStream(Stream stream);
    public BufferedStream(Stream stream, int bufferSize);
    // Fields
    private int _bufferSize;
    private Stream _s;
}

 

2.在Enterprise Library中的DAAB中有一个DbCommandWrapper的包装类,它实现了对IDbCommand类的包装并提供了参数处理的功能。结构图如下:

图8.Enterprise Library中的DbCommandWrapper使用来了装饰模式

示意性代码如下:

1
2
3
4
5
6
7
8
9
10
11
public abstract class DBCommandWrapper : MarshalByRefObject, IDisposable
{
}
  
public class SqlCommandWrapper : DBCommandWrapper
{
}
  
public class OracleCommandWrapper : DBCommandWrapper
{
}

另一方面,.NET 3.0中新增的扩展方法是这种模式的近亲,扩展方法也提供了给现有的类型(甚至类型是封闭的)增加功能的能力。类似的,用于WPF中的附加属性附加事件也可以在不改变类型本身的情况下动态扩展类型的功能。

 

效果及实现要点

 

  1. Component类在Decorator模式中充当抽象接口的角色,不应该去实现具体的行为。而且Decorator类对于Component类应该透明,换言之Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能。

  2. Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。我们可以使用一个或者多个Decorator对象来"装饰"一个Component对象,且装饰后的对象仍然是一个Component对象。

  3. Decorator模式并非解决"多子类衍生的多继承"问题,Decorator模式的应用要点在于解决"主体类在多个方向上的扩展功能"&mdash;&mdash;是为"装饰"的含义。

  4. 对于Decorator模式在实际中的运用可以很灵活。如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。

图9.没有Component的装饰模式的结构图

同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把DecoratorConcreteDecorator的责任合并成一个类。

图10.没有Decorator的装饰模式的结构图

  1. Decorator模式的优点是提供了比继承更加灵活的扩展,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。

  2. 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

  3. 装饰模式是利用SetComponent来对对象进行包装,每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。

  4. 通过装饰模式可以将类中的装饰功能从类中抽离出来,从而简化原有的类。也能够有效地把类的核心职责和装饰功能区分开。同时也能够去除相关类中重复的装饰逻辑。

最后特别需要注意的是,对原对象的装饰顺序在业务层面来讲可能会很重要。

 

来自《大话设计模式》的例子

这个例子中实现了一个虚拟装扮系统,各种衣服被作为不同的ConcreteDecorator装饰到人身上。如上面所提到,例子中Component只有人本身,所以就省略了Component,Decorator直接继承自ConcreteComponent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System;
  
class Program
{
    static void Main(string[] args)
    {
        Person xc = new Person("小菜");
  
        Console.WriteLine("\n第一种装扮:");
  
        Sneakers pqx = new Sneakers();
        BigTrouser kk = new BigTrouser();
        TShirts dtx = new TShirts();
  
        pqx.Decorate(xc);
        kk.Decorate(pqx);
        dtx.Decorate(kk);
        dtx.Show();
  
        Console.WriteLine("\n第二种装扮:");
  
        LeatherShoes px = new LeatherShoes();
        Tie ld = new Tie();
        Suit xz = new Suit();
  
        px.Decorate(xc);
        ld.Decorate(px);
        xz.Decorate(ld);
        xz.Show();
  
        Console.WriteLine("\n第三种装扮:");
        Sneakers pqx2 = new Sneakers();
        LeatherShoes px2 = new LeatherShoes();
        BigTrouser kk2 = new BigTrouser();
        Tie ld2 = new Tie();
  
        pqx2.Decorate(xc);
        px2.Decorate(pqx);
        kk2.Decorate(px2);
        ld2.Decorate(kk2);
  
        ld2.Show();
  
        Console.Read();
    }
}
  
class Person
{
    public Person()
    { }
  
    private string name;
    public Person(string name)
    {
        this.name = name;
    }
  
    public virtual void Show()
    {
        Console.WriteLine("装扮的{0}", name);
    }
}
  
class Finery : Person
{
    protected Person component;
  
    //打扮
    public void Decorate(Person component)
    {
        this.component = component;
    }
  
    public override void Show()
    {
        if (component != null)
        {
            component.Show();
        }
    }
}
  
  
class TShirts : Finery
{
    public override void Show()
    {
        Console.Write("大T恤 ");
        base.Show();
    }
}
  
class BigTrouser : Finery
{
    public override void Show()
    {
        Console.Write("垮裤 ");
        base.Show();
    }
}
  
class Sneakers : Finery
{
    public override void Show()
    {
        Console.Write("破球鞋 ");
        base.Show();
    }
}
  
class Suit : Finery
{
    public override void Show()
    {
        Console.Write("西装 ");
        base.Show();
    }
}
  
class Tie : Finery
{
    public override void Show()
    {
        Console.Write("领带 ");
        base.Show();
    }
}
  
class LeatherShoes : Finery
{
    public override void Show()
    {
        Console.Write("皮鞋 ");
        base.Show();
    }
}

 

总结

Decorator模式采用对象组合而非继承的手法,实现了在运行时动态的扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的"灵活性差"和"多子类衍生问题"。同时它很好地符合面向对象设计原则中"优先使用对象组合而非继承"和"开放-封闭"原则。

posted @   hystar  阅读(206)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
历史上的今天:
2012-06-01 WPF,Silverlight与XAML读书笔记第二十四 - 控件之六 – Items控件之菜单&其它
点击右上角即可分享
微信分享提示