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

来源:互联网 发布:重大电气知乎 编辑:程序博客网 时间:2024/06/05 03:51

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++设计模式》网络课程

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