装饰,即为扩展。为一个主体扩展一些其他操作。而装饰中间类即为扩展提供了一个规范,如此变可以通过组合的方式,完整复杂的多功能扩展。
代码:流操作
第一个版本
流操作的需求
- 流的类型:文件流、网络流、内存流
- 流的操作:加密、缓存
对于这些流操作的需求,我们很容易想到要有一个基类。
//业务操作class Stream {public:virtual char Read(int number) = 0;virtual void Seek(int position) = 0;virtual void Write(char data) = 0;virtual ~Stream(){}}//主体类class FileStream : public Stream { //文件流public:virtual char Read(int number) {//读文件流}virtual void Seek(int position) {//定位文件流}virtual void Write(char data) {//写文件流}}class NetworkStream : public Stream {public:virtual char Read(int number) {//读网络流}virtual void Seek(int position) {//定位网络流}virtual void Write(char data) {//写网络流}}class MemoryStream : public Stream{public:virtual char Read(int number) {//读内存流}virtual void Seek(int position) {//定位内存流}virtual void Write(char data) {//写内存流}}//第一种扩展操作:加密class CryptoFileStream :public FileStream{ //对文件流加密public:virtual char Read(int number){//额外的加密操作...FileStream::Read(number); //读文件流}virtual void Seek(int position){//额外的加密操作...FileStream::Seek(position); //定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...FileStream::Write(data); //写文件流//额外的加密操作...}};class CryptoNetworkStream :public NetworkStream{ //对网络流加密public:virtual char Read(int number){//额外的加密操作...NetworkStream::Read(number);//读网络流}virtual void Seek(int position){//额外的加密操作...NetworkStream::Seek(position);//定位网络流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...NetworkStream::Write(data);//写网络流//额外的加密操作...}};class CryptoMemoryStream : public MemoryStream{ //内存流的加密操作public:virtual char Read(int number){//额外的加密操作...MemoryStream::Read(number);//读内存流}virtual void Seek(int position){//额外的加密操作...MemoryStream::Seek(position);//定位内存流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...MemoryStream::Write(data);//写内存流//额外的加密操作...}};//第二种扩展操作:缓存操作class BufferedFileStream : public FileStream{//...};class BufferedNetworkStream : public NetworkStream{//...};class BufferedMemoryStream : public MemoryStream{//...}//第三种扩展操作:既加密,又缓存class CryptoBufferedFileStream :public FileStream{public:virtual char Read(int number){//额外的加密操作...//额外的缓冲操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...//额外的缓冲操作...FileStream::Seek(position);//定位文件流//额外的加密操作...//额外的缓冲操作...}virtual void Write(byte data){//额外的加密操作...//额外的缓冲操作...FileStream::Write(data);//写文件流//额外的加密操作...//额外的缓冲操作...}};void Process(){//编译时装配CryptoFileStream *fs1 = new CryptoFileStream();BufferedFileStream *fs2 = new BufferedFileStream();CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();}
继承关系:
问题一:类的规模
- Stream这一层是1,假设第二层的变化是n个,第三层的变化是m
- 不是m*n,他们可能有组合的情况,既加密又缓存
- 第三层结点个数:
n*(m+ m-1 + m-2 + m-3 ... + 1)
问题二:代码冗余
- 加密操作其实都是一样的(
CryptoMemoryStream::Read、CryptoNetworkStream::Read、CryptoFileStream::Read三者的加密操作其实是一样的) - 缓存的操作其实都是一样的
优化:去除代码冗余
根据组合优于继承原则,我们将继承改为组合
//...//第一种扩展操作:加密class CryptoFileStream /*:public FileStream*///删除继承关系{FileStream* stream; //改为组合关系public:virtual char Read(int number){//额外的加密操作...//FileStream::Read(number); //调用父类读文件流的方法//改为直接用字段的函数stream->Read(number);}//...};class CryptoNetworkStream /*:public NetworkStream*/{NetworkStream* stream;public:virtual char Read(int number){//额外的加密操作...//NetworkStream::Read(number);//读网络流stream->Read(number);}//...};class CryptoMemoryStream /*: public MemoryStream*/{MemoryStream* stream;public:virtual char Read(int number){//额外的加密操作...//MemoryStream::Read(number);//读内存流stream->Read(number);}//...};
优化:使用父类多态
当一个变量的声明类型有共同的父类时,我们可以直接声明为它们的父类——马丁·福勒经典重构理论
在上个版本中,CryptoFileStream::stream、CryptoNetworkStream::stream、CryptoMemoryStream::stream三个变量的声明类型有共同的父类,即Stream。
我们可以直接把它们写成Stream类型的指针,通过多态来调用函数
在未来,使用的时候,再传具体的类型(在运行时指定具体类型,编译时不指定)
//...//第一种扩展操作:加密class CryptoFileStream{Stream* stream; //在未来,赋值为FileStream。即= new FileStream();public:virtual char Read(int number){//额外的加密操作...stream->Read(number);}//...};class CryptoNetworkStream{Stream* stream; // = new NetworkStream();public:virtual char Read(int number){//额外的加密操作...stream->Read(number);}//...};class CryptoMemoryStream{Stream* stream; // = new MemoryStream();public:virtual char Read(int number){//额外的加密操作...stream->Read(number);}//...};
优化:抽出父类(第二个大版本)
经过上两个版本的迭代,发现CryptoFileStream、CryptoNetworkStream、CryptoMemoryStream变成一样了。只不过字段stream的具体类型需要在运行时指定。
class CryptoStream: public Stream //继承Stream基类,为了让Read、Seek、Write函数满足接口规范(和stream的函数一样),成为一个虚函数{Stream* stream; //运行时再指定,编译时声明为基类指针public:CryptoStream(Stream* stm):stream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流(多态调用)}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流(多态调用)//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流(多态调用)//额外的加密操作...}};
至此,问题已经得到了极大的缓解了。以下是此次迭代的完整代码
//业务操作class Stream{public:virtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}};//主体类class FileStream: public Stream{public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//第一种扩展操作:加密class CryptoStream: public Stream {Stream* stream;//...public:CryptoStream(Stream* stm):stream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}};//第二种扩展操作:缓存class BufferedStream : public Stream{Stream* stream;//...public:BufferedStream(Stream* stm):stream(stm){}//...};//运行时装配//1. 编译不存在加密文件流、缓存文件流、加密缓存文件流。我们没有那样的类//2. 但是在运行的时候,我们可以通过组合的方式把它们装配起来,满足我们的需求void Process(){//创建一个文件流FileStream* s1 = new FileStream();//传入s1文件流,s2为文件流的加密操作CryptoStream* s2 = new CryptoStream(s1);//传入s1文件流,s3为文件流的缓存操作BufferedStream* s3 = new BufferedStream(s1);//传入s2文件流加密操作,s4为文件流加密操作 + 缓存操作BufferedStream* s4 = new BufferedStream(s2);}
优化:再抽象出父类,即装饰器(第三个大版本)
其实不难看出,CryptoStream、BufferedStream还存在冗余代码。有着一个相同的成员变量。
如果某一个类的多个子类有着共同的字段,我们可以把这个字段往上提一层——马丁·福勒经典重构理论
Stream的子类CryptoStream、BufferedStream有着共同的字段Stream* stream,所以我们可以把它们往上再提一层
我们有两种提法。
一、第一种提法:把Stream* stream提到基类中(即Stream里)
class Stream{Stream* stream;public:virtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}};
提到父类Stream中,但这会有一个问题。主体类FileStream、NetworkStream、MemoryStream都不需要这个字段
二、第二种提法:设计一个中间类
//中间的装饰类class DecoratorStream: public Stream{protected: //子类要用,申明为protectedStream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};
第二种提法是可以的,其实就是我们的装饰设计模式
完整代码:
//业务操作class Stream{public:virtual char Read(int number)=0;virtual void Seek(int position)=0;virtual void Write(char data)=0;virtual ~Stream(){}};//主体类class FileStream: public Stream{public:virtual char Read(int number){//读文件流}virtual void Seek(int position){//定位文件流}virtual void Write(char data){//写文件流}};class NetworkStream :public Stream{public:virtual char Read(int number){//读网络流}virtual void Seek(int position){//定位网络流}virtual void Write(char data){//写网络流}};class MemoryStream :public Stream{public:virtual char Read(int number){//读内存流}virtual void Seek(int position){//定位内存流}virtual void Write(char data){//写内存流}};//提供一个中间装饰类,为Stream的扩展操作提供一个基类DecoratorStream: public Stream{protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};//扩展操作一:加密操作class CryptoStream: public DecoratorStream {public:CryptoStream(Stream* stm):DecoratorStream(stm){}virtual char Read(int number){//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}};//扩展操作二:缓存操作class BufferedStream : public DecoratorStream{//与CryptoStream类似};void Process(){//运行时装配FileStream* s1=new FileStream(); //只需要一个文件流,没有加密、缓存的操作//加密流必须传入一个流进来,这就是扩展操作,你在谁的基础上再做操作//这就是装饰的含义,附着在其他之上的操作CryptoStream* s2=new CryptoStream(s1); //文件加密流BufferedStream* s3=new BufferedStream(s1); //文件缓存流BufferedStream* s4=new BufferedStream(s2);}
说明:装饰的含义,就是附着在本体之上的其他操作,即扩展操作。所以如果你要使用装饰的话,必须指定其本体是谁
例如:
FileStream* s1=new FileStream();//只需要一个文件流,没有加密、缓存的操作//加密流(加密流是装饰类的子类),它必须传入一个流进来//这就是扩展操作,你在谁的基础上再做操作//这就是装饰的含义,附着在本体之上的操作CryptoStream* s2=new CryptoStream(s1); //文件加密流
| 第一个版本 | 第三个版本(装饰模式) | |
|---|---|---|
| 类图 | ![]() |
![]() |
| 类规模 | 1+n+n*m!/2 |
**1+n+1+m** |
为什么两个版本类规模差异如此之大?
- 第一个版本背后有很多重复代码
- 是对继承的不良使用,使用组合更好。用组合的方式支持未来的一种变化
**
举例:每个英雄都可以放QWER技能
动机
在某些情况下我们可能会“过渡地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 静态特质:第一个版本
CryptoFileStream类中FileStream::Read(number);,这永远是调用FileStream的Read函数,是不会变化的代码。这个是由继承实现的 - 动态特质:在第三个版本中,
CryptoStream类中的stream->Read(number)。这句代码是用多态指针来调虚函数,这句话就有变化了。这个是由组合实现的
如何使“对象功能的扩展”能够根据需要来动态地实现?
同时避免“扩展功能的增多”带来子类膨胀问题?
从而使得任何“功能扩展变化”所导致的影响降为最低?
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。——《设计模式》GOF
结构

要点总结
要点一: 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”
要点二:Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类
- Decorator类既继承Stream,又组合Stream(内部有一个Stream类型的多态指针)
- 如果一个类B继承与A类,又具有一个A类的指针 => 99%是Decorator设计模式 => 正常情况不会这么干,要不就只有继承,要不就只有组合,不会两个都有;两个都有的情况一般都是装饰模式
- 组合Steam是为了什么?将来支持调用Stream的具体实现类(如FileStream、NetworkStream、MemoryStream)
- 继承Stream是为了什么?把它自己也声明为一个流,为了让Read、Seek、Write函数满足接口规范(和stream的函数一样),成为一个虚函数
- 有时候你可能看不到人家的内部字段(看不到人家有组合)。但类B继承于A,B的构造函数里还需要传A的指针,你也能判断B内部有A,B和A也有组合关系
要点三:Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为了“装饰”的含义
- FileStream和Stream是继承关系,但是CryptoFileStream和FileStream是继承关系嘛?非要继承吗?
- 不是的,它俩(Stream->FileStream;FileStream->CryptoFileStream)都不是一个变化的方向
- 实际上FileStream->CryptoFileStream是扩展操作,是扩展的方向;而Stream->FileStream是主体操作
- 主体操作和扩展操作应该分开成两个分支进行继承


