[设计模式] 设计模式课程(五)--装饰模式
场景
- 按目的划分,属于结构型模式;按封装划分,属于单一职责模式
- 使用继承来扩展对象的功能时,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性,并且随着子类的增多,各种子类的组合会导致更多子类的膨胀
- 如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
- 如果责任划分不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时的关键是划分责任
- 将对象放入包含行为的特殊封装对象中,为原来的对象绑定新的行为
- 通知基类与装饰器实现相同的接口
- 采用组合的方式实现功能扩展,即装饰类实现的接口和其构造函数传入的对象是同一接口
- 在无需修改原对象代码的情况下,为对象增加额外的行为
- 对于用final关键字限制扩展的类,只能通过装饰模式进行修饰
结构
- 部件接口:声明装饰器和被封装对象的公用接口
- 具体部件类:被封装对象的类,定义可被装饰的基本行为
- 基础装饰类:包含指向被封装对象的引用成员变量
- 具体装饰类:定义了可动态添加到部件的额外行为,重写装饰基类的方法,添加额外行为
场景
- 实现消息通知功能时,将邮件通知行为放在基类通知器中,将其他通知方法(QQ、微信、短信)放在装饰器中,客户代码将通知器放入自己需要的装饰器中
- 当数据即将被写入磁盘前, 通过装饰器对数据进行加密和压缩。 在原始类对改变毫无察觉的情况下, 将加密后的受保护数据写入文件
- 当数据刚从磁盘读出后, 同样通过装饰对数据进行解压和解密。 装饰和数据源类实现同一接口, 从而能在客户端代码中相互替换
示例1
decorator1.cpp
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //业务操作 2 class Stream{ 3 public: 4 virtual char Read(int number)=0; 5 virtual void Seek(int position)=0; 6 virtual void Write(char data)=0; 7 8 virtual ~Stream(){} 9 }; 10 11 //主体类 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //读文件流 16 } 17 virtual void Seek(int position){ 18 //定位文件流 19 } 20 virtual void Write(char data){ 21 //写文件流 22 } 23 24 }; 25 26 class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //读网络流 30 } 31 virtual void Seek(int position){ 32 //定位网络流 33 } 34 virtual void Write(char data){ 35 //写网络流 36 } 37 38 }; 39 40 class MemoryStream :public Stream{ 41 public: 42 virtual char Read(int number){ 43 //读内存流 44 } 45 virtual void Seek(int position){ 46 //定位内存流 47 } 48 virtual void Write(char data){ 49 //写内存流 50 } 51 52 }; 53 54 //扩展操作 55 class CryptoFileStream :public FileStream{ 56 public: 57 virtual char Read(int number){ 58 59 //额外的加密操作... 60 FileStream::Read(number);//读文件流 61 62 } 63 virtual void Seek(int position){ 64 //额外的加密操作... 65 FileStream::Seek(position);//定位文件流 66 //额外的加密操作... 67 } 68 virtual void Write(byte data){ 69 //额外的加密操作... 70 FileStream::Write(data);//写文件流 71 //额外的加密操作... 72 } 73 }; 74 75 class CryptoNetworkStream : :public NetworkStream{ 76 public: 77 virtual char Read(int number){ 78 79 //额外的加密操作... 80 NetworkStream::Read(number);//读网络流 81 } 82 virtual void Seek(int position){ 83 //额外的加密操作... 84 NetworkStream::Seek(position);//定位网络流 85 //额外的加密操作... 86 } 87 virtual void Write(byte data){ 88 //额外的加密操作... 89 NetworkStream::Write(data);//写网络流 90 //额外的加密操作... 91 } 92 }; 93 94 class CryptoMemoryStream : public MemoryStream{ 95 public: 96 virtual char Read(int number){ 97 98 //额外的加密操作... 99 MemoryStream::Read(number);//读内存流 100 } 101 virtual void Seek(int position){ 102 //额外的加密操作... 103 MemoryStream::Seek(position);//定位内存流 104 //额外的加密操作... 105 } 106 virtual void Write(byte data){ 107 //额外的加密操作... 108 MemoryStream::Write(data);//写内存流 109 //额外的加密操作... 110 } 111 }; 112 113 class BufferedFileStream : public FileStream{ 114 //... 115 }; 116 117 class BufferedNetworkStream : public NetworkStream{ 118 //... 119 }; 120 121 class BufferedMemoryStream : public MemoryStream{ 122 //... 123 } 124 125 class CryptoBufferedFileStream :public FileStream{ 126 public: 127 virtual char Read(int number){ 128 129 //额外的加密操作... 130 //额外的缓冲操作... 131 FileStream::Read(number);//读文件流 132 } 133 virtual void Seek(int position){ 134 //额外的加密操作... 135 //额外的缓冲操作... 136 FileStream::Seek(position);//定位文件流 137 //额外的加密操作... 138 //额外的缓冲操作... 139 } 140 virtual void Write(byte data){ 141 //额外的加密操作... 142 //额外的缓冲操作... 143 FileStream::Write(data);//写文件流 144 //额外的加密操作... 145 //额外的缓冲操作... 146 } 147 }; 148 149 void Process(){ 150 151 //编译时装配 152 CryptoFileStream *fs1 = new CryptoFileStream(); 153 154 BufferedFileStream *fs2 = new BufferedFileStream(); 155 156 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 157 158 }
- 问题:Stream规模太大,60,80,99是一样的操作,产生代码冗余
decorator2.cpp
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //业务操作 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 class CryptoStream: public Stream { 57 58 Stream* stream;//未来可变成各种stream,支持变化 59 60 public: 61 CryptoStream(Stream* stm):stream(stm){ 62 63 } 64 65 virtual char Read(int number){ 66 67 //额外的加密操作... 68 stream->Read(number);//读文件流 69 } 70 virtual void Seek(int position){ 71 //额外的加密操作... 72 stream::Seek(position);//定位文件流 73 //额外的加密操作... 74 } 75 virtual void Write(byte data){ 76 //额外的加密操作... 77 stream::Write(data);//写文件流 78 //额外的加密操作... 79 } 80 }; 81 82 class BufferedStream : public Stream{ 83 84 Stream* stream;//... 85 86 public: 87 BufferedStream(Stream* stm):stream(stm){ 88 89 } 90 //... 91 }; 92 93 void Process(){ 94 //运行时装配 95 FileStream* s1=new FileStream(); 96 97 //加密 98 CryptoStream* s2=new CryptoStream(s1); 99 100 //缓存 101 BufferedStream* s3=new BufferedStream(s1); 102 103 //既加密又缓存 104 BufferedStream* s4=new BufferedStream(s2); 105 }
- 用组合代替继承,合并子类,消除重复性
- CryptoStream类既继承了Stream(完善接口规范),又有stream字段(别忘写构造函数)
- FileStream::Read(number); 和 stream->Read(number); 效果一样
- 实际代码看到组合和继承同时出现在一个类中时,99%是装饰器模式
- 编译时一样(复用),运行时不一样(组合,用多态支持变化)
- 编译使装配(decorator1) vs. 运行时装配(decorator2)
- 当变量声明类型都是某个类型的子类时,直接声明成某个类型即可
decorator3.cpp
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //业务操作 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 public: 70 CryptoStream(Stream* stm):DecoratorStream(stm){ 71 72 } 73 74 75 virtual char Read(int number){ 76 77 //额外的加密操作... 78 stream->Read(number);//读文件流 79 } 80 virtual void Seek(int position){ 81 //额外的加密操作... 82 stream::Seek(position);//定位文件流 83 //额外的加密操作... 84 } 85 virtual void Write(byte data){ 86 //额外的加密操作... 87 stream::Write(data);//写文件流 88 //额外的加密操作... 89 } 90 }; 91 92 class BufferedStream : public DecoratorStream{ 93 94 Stream* stream;//... 95 96 public: 97 BufferedStream(Stream* stm):DecoratorStream(stm){ 98 99 } 100 //... 101 }; 102 103 void Process(){ 104 105 //运行时装配 106 FileStream* s1=new FileStream(); 107 108 CryptoStream* s2=new CryptoStream(s1); 109 110 BufferedStream* s3=new BufferedStream(s1); 111 112 BufferedStream* s4=new BufferedStream(s2); 113 114 }
- 有相同的字段,尽量往上(基类)提(decorator3)
- 设计一个中间类DecoratorStream,继承Stream
- decorator1的类个数:1+n+n*m!/2
- decorator1的类个数:1+n+1+m
示例2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
联系
- 适配器模式可对已有对象接口进行修改,装饰模式可在不改变对象接口的前提下强化对象功能
- 装饰模式可改变对象的外表,策略模式可改变对象的本质
- 装饰和代理:都基于组合原则,即一个对象将部分工作委派给另一对象,但代理通常自行管理其服务对象的生命周期,而装饰的生成则总是由客户端进行控制
- 装饰和组合:都可通过递归组织无限数量的对象,但装饰仅有一个子组件,且为被封装对象添加了额外职责,而组合仅对子节点的结果进行求和
- 装饰和责任链:都依赖递归组合将需要执行的操作传递给一系列对象,但责任链的管理者可以独立地执行一切操作,还可以随时停止传递请求,而装饰仅可在遵循基本接口的情况下扩展对象的行为,且无法中断请求的传递
总结
- 动态(组合)地给一个对象增加一些额外职责,就增加功能而言,Decorator模式比继承更为灵活(消除重复代码 & 减少子类个数)
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所有的接口。但实际上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类
- 主体操作和扩展操作应该分开
参考