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++设计模式》网络课程
《设计模式:可复用面向对象软件的基础》