首页 > 代码库 > c++ 设计模式6 (Decorator 装饰模式)

c++ 设计模式6 (Decorator 装饰模式)

4. “单一职责”类模式

在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。

典型模式代表: Decorator,Bridge

 

4.1 Decorator 装饰模式

 

代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现

实现代码1:

类图结构示意(大量使用继承)

技术分享

数据规模: 假设有n种文件,m种功能操作。该实现方法有(1 + n + n * m! / 2) 数量级的子类;

同时考察59行,79行,98行本身是相同的代码(类似还有很多),存在大量的冗余和重复。

开始重构,见方法2.

技术分享
  1 //Decorator1.cpp
  2 //业务操作
  3 class Stream{
  4 public:
  5     virtual char Read(int number)=0;
  6     virtual void Seek(int position)=0;
  7     virtual void Write(char data)=0;
  8     
  9     virtual ~Stream(){}
 10 };
 11 
 12 //主体类
 13 class FileStream: public Stream{
 14 public:
 15     virtual char Read(int number){
 16         //读文件流
 17     }
 18     virtual void Seek(int position){
 19         //定位文件流
 20     }
 21     virtual void Write(char data){
 22         //写文件流
 23     }
 24 
 25 };
 26 
 27 class NetworkStream :public Stream{
 28 public:
 29     virtual char Read(int number){
 30         //读网络流
 31     }
 32     virtual void Seek(int position){
 33         //定位网络流
 34     }
 35     virtual void Write(char data){
 36         //写网络流
 37     }
 38     
 39 };
 40 
 41 class MemoryStream :public Stream{
 42 public:
 43     virtual char Read(int number){
 44         //读内存流
 45     }
 46     virtual void Seek(int position){
 47         //定位内存流
 48     }
 49     virtual void Write(char data){
 50         //写内存流
 51     }
 52     
 53 };
 54 
 55 //扩展操作
 56 class CryptoFileStream :public FileStream{
 57 public:
 58     virtual char Read(int number){
 59        
 60         //额外的加密操作...
 61         FileStream::Read(number);//读文件流
 62         
 63     }
 64     virtual void Seek(int position){
 65         //额外的加密操作...
 66         FileStream::Seek(position);//定位文件流
 67         //额外的加密操作...
 68     }
 69     virtual void Write(byte data){
 70         //额外的加密操作...
 71         FileStream::Write(data);//写文件流
 72         //额外的加密操作...
 73     }
 74 };
 75 
 76 class CryptoNetworkStream : :public NetworkStream{
 77 public:
 78     virtual char Read(int number){
 79         
 80         //额外的加密操作...
 81         NetworkStream::Read(number);//读网络流
 82     }
 83     virtual void Seek(int position){
 84         //额外的加密操作...
 85         NetworkStream::Seek(position);//定位网络流
 86         //额外的加密操作...
 87     }
 88     virtual void Write(byte data){
 89         //额外的加密操作...
 90         NetworkStream::Write(data);//写网络流
 91         //额外的加密操作...
 92     }
 93 };
 94 
 95 class CryptoMemoryStream : public MemoryStream{
 96 public:
 97     virtual char Read(int number){
 98         
 99         //额外的加密操作...
100         MemoryStream::Read(number);//读内存流
101     }
102     virtual void Seek(int position){
103         //额外的加密操作...
104         MemoryStream::Seek(position);//定位内存流
105         //额外的加密操作...
106     }
107     virtual void Write(byte data){
108         //额外的加密操作...
109         MemoryStream::Write(data);//写内存流
110         //额外的加密操作...
111     }
112 };
113 
114 class BufferedFileStream : public FileStream{
115     //...
116 };
117 
118 class BufferedNetworkStream : public NetworkStream{
119     //...
120 };
121 
122 class BufferedMemoryStream : public MemoryStream{
123     //...
124 }
125 
126 
127 
128 
129 class CryptoBufferedFileStream :public FileStream{
130 public:
131     virtual char Read(int number){
132         
133         //额外的加密操作...
134         //额外的缓冲操作...
135         FileStream::Read(number);//读文件流
136     }
137     virtual void Seek(int position){
138         //额外的加密操作...
139         //额外的缓冲操作...
140         FileStream::Seek(position);//定位文件流
141         //额外的加密操作...
142         //额外的缓冲操作...
143     }
144     virtual void Write(byte data){
145         //额外的加密操作...
146         //额外的缓冲操作...
147         FileStream::Write(data);//写文件流
148         //额外的加密操作...
149         //额外的缓冲操作...
150     }
151 };
152 
153 
154 
155 void Process(){
156 
157         //编译时装配
158     CryptoFileStream *fs1 = new CryptoFileStream();
159 
160     BufferedFileStream *fs2 = new BufferedFileStream();
161 
162     CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
163 
164 }
技术分享

 

实现代码2:

针对上述代码,重构步骤如下:

1)考察 CryptoFileStream ,CryptoNetworkStream,CryptoMemoryStream三个类,将其继承FileStream,NetworkStream,NetworkStream改为组合;即

技术分享
 1 class CryptoFileStream{
 2     FileStream* stream;
 3 public:
 4     virtual char Read(int number){
 5        
 6         //额外的加密操作...
 7         stream -> Read(number);//改用字段方式调用Read()
 8         // ...seek() write() 同理
 9     }
10 }    
11 
12 class CryptoNetworkStream{
13     NetworkStream* stream;
14 public:
15     virtual char Read(int number){
16         
17         //额外的加密操作...
18         stream -> Read(number);//改用字段方式调用Read()
19         //... seek() write() 同理
20     }
21 }    
22 
23 class CryptoMemoryStream{
24     MemoryStream* stream;
25 public:
26     virtual char Read(int number){
27         
28         //额外的加密操作...
29         stream -> Read(number);//改用字段方式调用Read()
30         //... seek() write() 同理
31     }
32 }    
技术分享

2)考察上述2行, 13行, 24行, 发现其均为Stream子类, 应使用多态性继续重构。

技术分享
 1 class CryptoFileStream{
 2     Stream* stream; // = new FileStream()
 3 public:
 4     virtual char Read(int number){
 5        
 6         //额外的加密操作...
 7         stream -> Read(number);//改用字段方式调用Read()
 8         // ...seek() write() 同理
 9     }
10 }    
11 
12 class CryptoNetworkStream{
13     Stream* stream; // = new NetworkStream();
14 public:
15     virtual char Read(int number){
16         
17         //额外的加密操作...
18         stream -> Read(number);//改用字段方式调用Read()
19         //... seek() write() 同理
20     }
21 }    
22 
23 class CryptoMemoryStream{
24     Stream* stream; // = newMemoryStream()
25 public:
26     virtual char Read(int number){
27         
28         //额外的加密操作...
29         stream -> Read(number);//改用字段方式调用Read()
30         //... seek() write() 同理
31     }
32 }    
技术分享

3)发现三个类是相同的,不同的实现(需求的变化)是在运行时实现,编译时复用,改为一个类即可,命名为CryptoStream。

同时为了保证接口规范(read,seek等仍然是虚函数),继承Stream,出现既有组合,又有继承的情况。

技术分享
 1 class CryptoStream : public Stream{
 2     Stream* stream; // = new ...
 3 public:
 4     virtual char Read(int number){
 5        
 6         //额外的加密操作...
 7         stream -> Read(number);//改用字段方式调用Read()
 8         // ...seek() write() 同理
 9     }
10 }   
技术分享

4)添加相应构造器,得到此轮重构后的结果,代码如下,主要查看使用方式(运行时装配):

技术分享
  1 //Decorator2.cpp
  2 class Stream{
  3 
  4 public:
  5     virtual char Read(int number)=0;
  6     virtual void Seek(int position)=0;
  7     virtual void Write(char data)=0;
  8     
  9     virtual ~Stream(){}
 10 };
 11 
 12 //主体类
 13 class FileStream: public Stream{
 14 public:
 15     virtual char Read(int number){
 16         //读文件流
 17     }
 18     virtual void Seek(int position){
 19         //定位文件流
 20     }
 21     virtual void Write(char data){
 22         //写文件流
 23     }
 24 
 25 };
 26 
 27 class NetworkStream :public Stream{
 28 public:
 29     virtual char Read(int number){
 30         //读网络流
 31     }
 32     virtual void Seek(int position){
 33         //定位网络流
 34     }
 35     virtual void Write(char data){
 36         //写网络流
 37     }
 38     
 39 };
 40 
 41 class MemoryStream :public Stream{
 42 public:
 43     virtual char Read(int number){
 44         //读内存流
 45     }
 46     virtual void Seek(int position){
 47         //定位内存流
 48     }
 49     virtual void Write(char data){
 50         //写内存流
 51     }
 52     
 53 };
 54 
 55 //扩展操作
 56 
 57 
 58 class CryptoStream: public Stream {
 59     
 60     Stream* stream;//...
 61 
 62 public:
 63     CryptoStream(Stream* stm):stream(stm){
 64     
 65     }
 66     
 67     
 68     virtual char Read(int number){
 69        
 70         //额外的加密操作...
 71         stream->Read(number);//读文件流
 72     }
 73     virtual void Seek(int position){
 74         //额外的加密操作...
 75         stream::Seek(position);//定位文件流
 76         //额外的加密操作...
 77     }
 78     virtual void Write(byte data){
 79         //额外的加密操作...
 80         stream::Write(data);//写文件流
 81         //额外的加密操作...
 82     }
 83 };
 84 
 85 
 86 
 87 class BufferedStream : public Stream{
 88     
 89     Stream* stream;//...
 90     
 91 public:
 92     BufferedStream(Stream* stm):stream(stm){
 93         
 94     }
 95     //...
 96 };
 97 
 98 
 99 
100 
101 
102 void Process(){
103 
104     //运行时装配
105     FileStream* s1=new FileStream();
106     CryptoStream* s2=new CryptoStream(s1);
107     
108     BufferedStream* s3=new BufferedStream(s1);
109     
110     BufferedStream* s4=new BufferedStream(s2);
111     
112     
113 
114 }
技术分享

 

实现代码3:

上述实现代码2已经极大地缓解了冗余问题,符合面向对象的设计思想,该轮重构是锦上添花。

重构步骤如下:

考察上述代码,多个子类都有同样的字段(Stream* stream;//...)

应考虑“往上提”,方法有两种,第一种是提到基类(显然不合适,FileStream等并不需要Stream字段 )

所以考虑第二种方法,实现一个“中间类”。

技术分享
DecoratorStream: public Stream{
protected:
    Stream* stream;//...
    
    DecoratorStream(Stream * stm):stream(stm){
    
    }
    
};
技术分享

CryptoStream等继承中间类DecoratorStream:

技术分享
class CryptoStream: public DecoratorStream {
 
public:
    CryptoStream(Stream* stm):DecoratorStream(stm){
    
    }
    //...
}    
技术分享

重构完成的最终版本:

FileStream,NetworkStream,MemoryStream等可以创建各自的对象;

但实现加密,缓存功能必须在已有FileStream/NetworkStream等对象基础上;

这些操作本质是扩展操作,也就是“装饰”的含义。

此时类图示意:

这时类的数量为(1 + n + 1 + m)

 

 

技术分享

 

 

 

技术分享
  1 //Decorator3.cpp
  2 class Stream{
  3 
  4 public:
  5     virtual char Read(int number)=0;
  6     virtual void Seek(int position)=0;
  7     virtual void Write(char data)=0;
  8     
  9     virtual ~Stream(){}
 10 };
 11 
 12 //主体类
 13 class FileStream: public Stream{
 14 public:
 15     virtual char Read(int number){
 16         //读文件流
 17     }
 18     virtual void Seek(int position){
 19         //定位文件流
 20     }
 21     virtual void Write(char data){
 22         //写文件流
 23     }
 24 
 25 };
 26 
 27 class NetworkStream :public Stream{
 28 public:
 29     virtual char Read(int number){
 30         //读网络流
 31     }
 32     virtual void Seek(int position){
 33         //定位网络流
 34     }
 35     virtual void Write(char data){
 36         //写网络流
 37     }
 38     
 39 };
 40 
 41 class MemoryStream :public Stream{
 42 public:
 43     virtual char Read(int number){
 44         //读内存流
 45     }
 46     virtual void Seek(int position){
 47         //定位内存流
 48     }
 49     virtual void Write(char data){
 50         //写内存流
 51     }
 52     
 53 };
 54 
 55 //扩展操作
 56 
 57 DecoratorStream: public Stream{
 58 protected:
 59     Stream* stream;//...
 60     
 61     DecoratorStream(Stream * stm):stream(stm){
 62     
 63     }
 64     
 65 };
 66 
 67 class CryptoStream: public DecoratorStream {
 68  
 69 
 70 public:
 71     CryptoStream(Stream* stm):DecoratorStream(stm){
 72     
 73     }
 74     
 75     
 76     virtual char Read(int number){
 77        
 78         //额外的加密操作...
 79         stream->Read(number);//读文件流
 80     }
 81     virtual void Seek(int position){
 82         //额外的加密操作...
 83         stream::Seek(position);//定位文件流
 84         //额外的加密操作...
 85     }
 86     virtual void Write(byte data){
 87         //额外的加密操作...
 88         stream::Write(data);//写文件流
 89         //额外的加密操作...
 90     }
 91 };
 92 
 93 
 94 
 95 class BufferedStream : public DecoratorStream{
 96     
 97     Stream* stream;//...
 98     
 99 public:
100     BufferedStream(Stream* stm):DecoratorStream(stm){
101         
102     }
103     //...
104 };
105 
106 
107 
108 
109 void Process(){
110 
111     //运行时装配
112     FileStream* s1=new FileStream();
113     
114     CryptoStream* s2=new CryptoStream(s1);
115     
116     BufferedStream* s3=new BufferedStream(s1);
117     
118     BufferedStream* s4=new BufferedStream(s2);
119     
120     
121 
122 }
技术分享

 

Decorator模式使用动机:

在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于基础为类型引入的静态特指,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各个子类的组合(扩展功能的组合)会导致各种子类的膨胀。

 

模式定义:

动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比声场子类(继承)更为灵活(消除重复代码&减少子类个数)

 

类图:

技术分享

 

要点总结:

1.通过采用组合并非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“

2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。

3.Decorator模式的目的并非解决”多字类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“(显然file,network与加密,缓冲是两种扩展方向) ——是为”装饰“的含义。

 

参考文献:

李建忠老师 《C++设计模式》网络课程

《设计模式:可复用面向对象软件的基础》

c++ 设计模式6 (Decorator 装饰模式)