装饰,即为扩展。为一个主体扩展一些其他操作。而装饰中间类即为扩展提供了一个规范,如此变可以通过组合的方式,完整复杂的多功能扩展。

代码:流操作

第一个版本

流操作的需求

  1. 流的类型:文件流、网络流、内存流
  2. 流的操作:加密、缓存

对于这些流操作的需求,我们很容易想到要有一个基类。

  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. virtual ~Stream(){}
  8. }
  9. //主体类
  10. class FileStream : public Stream { //文件流
  11. public:
  12. virtual char Read(int number) {
  13. //读文件流
  14. }
  15. virtual void Seek(int position) {
  16. //定位文件流
  17. }
  18. virtual void Write(char data) {
  19. //写文件流
  20. }
  21. }
  22. class NetworkStream : public Stream {
  23. public:
  24. virtual char Read(int number) {
  25. //读网络流
  26. }
  27. virtual void Seek(int position) {
  28. //定位网络流
  29. }
  30. virtual void Write(char data) {
  31. //写网络流
  32. }
  33. }
  34. class MemoryStream : public Stream{
  35. public:
  36. virtual char Read(int number) {
  37. //读内存流
  38. }
  39. virtual void Seek(int position) {
  40. //定位内存流
  41. }
  42. virtual void Write(char data) {
  43. //写内存流
  44. }
  45. }
  46. //第一种扩展操作:加密
  47. class CryptoFileStream :public FileStream{ //对文件流加密
  48. public:
  49. virtual char Read(int number){
  50. //额外的加密操作...
  51. FileStream::Read(number); //读文件流
  52. }
  53. virtual void Seek(int position){
  54. //额外的加密操作...
  55. FileStream::Seek(position); //定位文件流
  56. //额外的加密操作...
  57. }
  58. virtual void Write(byte data){
  59. //额外的加密操作...
  60. FileStream::Write(data); //写文件流
  61. //额外的加密操作...
  62. }
  63. };
  64. class CryptoNetworkStream :public NetworkStream{ //对网络流加密
  65. public:
  66. virtual char Read(int number){
  67. //额外的加密操作...
  68. NetworkStream::Read(number);//读网络流
  69. }
  70. virtual void Seek(int position){
  71. //额外的加密操作...
  72. NetworkStream::Seek(position);//定位网络流
  73. //额外的加密操作...
  74. }
  75. virtual void Write(byte data){
  76. //额外的加密操作...
  77. NetworkStream::Write(data);//写网络流
  78. //额外的加密操作...
  79. }
  80. };
  81. class CryptoMemoryStream : public MemoryStream{ //内存流的加密操作
  82. public:
  83. virtual char Read(int number){
  84. //额外的加密操作...
  85. MemoryStream::Read(number);//读内存流
  86. }
  87. virtual void Seek(int position){
  88. //额外的加密操作...
  89. MemoryStream::Seek(position);//定位内存流
  90. //额外的加密操作...
  91. }
  92. virtual void Write(byte data){
  93. //额外的加密操作...
  94. MemoryStream::Write(data);//写内存流
  95. //额外的加密操作...
  96. }
  97. };
  98. //第二种扩展操作:缓存操作
  99. class BufferedFileStream : public FileStream{
  100. //...
  101. };
  102. class BufferedNetworkStream : public NetworkStream{
  103. //...
  104. };
  105. class BufferedMemoryStream : public MemoryStream{
  106. //...
  107. }
  108. //第三种扩展操作:既加密,又缓存
  109. class CryptoBufferedFileStream :public FileStream{
  110. public:
  111. virtual char Read(int number){
  112. //额外的加密操作...
  113. //额外的缓冲操作...
  114. FileStream::Read(number);//读文件流
  115. }
  116. virtual void Seek(int position){
  117. //额外的加密操作...
  118. //额外的缓冲操作...
  119. FileStream::Seek(position);//定位文件流
  120. //额外的加密操作...
  121. //额外的缓冲操作...
  122. }
  123. virtual void Write(byte data){
  124. //额外的加密操作...
  125. //额外的缓冲操作...
  126. FileStream::Write(data);//写文件流
  127. //额外的加密操作...
  128. //额外的缓冲操作...
  129. }
  130. };
  131. void Process(){
  132. //编译时装配
  133. CryptoFileStream *fs1 = new CryptoFileStream();
  134. BufferedFileStream *fs2 = new BufferedFileStream();
  135. CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
  136. }

继承关系:
image.png

问题一:类的规模

  1. Stream这一层是1,假设第二层的变化是n个,第三层的变化是m
  2. 不是m*n,他们可能有组合的情况,既加密又缓存
  3. 第三层结点个数:n*(m+ m-1 + m-2 + m-3 ... + 1)

问题二:代码冗余

  1. 加密操作其实都是一样的(CryptoMemoryStream::ReadCryptoNetworkStream::ReadCryptoFileStream::Read三者的加密操作其实是一样的)
  2. 缓存的操作其实都是一样的

优化:去除代码冗余

根据组合优于继承原则,我们将继承改为组合

  1. //...
  2. //第一种扩展操作:加密
  3. class CryptoFileStream /*:public FileStream*/
  4. //删除继承关系
  5. {
  6. FileStream* stream; //改为组合关系
  7. public:
  8. virtual char Read(int number){
  9. //额外的加密操作...
  10. //FileStream::Read(number); //调用父类读文件流的方法
  11. //改为直接用字段的函数
  12. stream->Read(number);
  13. }
  14. //...
  15. };
  16. class CryptoNetworkStream /*:public NetworkStream*/
  17. {
  18. NetworkStream* stream;
  19. public:
  20. virtual char Read(int number){
  21. //额外的加密操作...
  22. //NetworkStream::Read(number);//读网络流
  23. stream->Read(number);
  24. }
  25. //...
  26. };
  27. class CryptoMemoryStream /*: public MemoryStream*/
  28. {
  29. MemoryStream* stream;
  30. public:
  31. virtual char Read(int number){
  32. //额外的加密操作...
  33. //MemoryStream::Read(number);//读内存流
  34. stream->Read(number);
  35. }
  36. //...
  37. };

优化:使用父类多态

当一个变量的声明类型有共同的父类时,我们可以直接声明为它们的父类——马丁·福勒经典重构理论

在上个版本中,CryptoFileStream::streamCryptoNetworkStream::streamCryptoMemoryStream::stream三个变量的声明类型有共同的父类,即Stream
我们可以直接把它们写成Stream类型的指针,通过多态来调用函数
在未来,使用的时候,再传具体的类型(在运行时指定具体类型,编译时不指定)

  1. //...
  2. //第一种扩展操作:加密
  3. class CryptoFileStream
  4. {
  5. Stream* stream; //在未来,赋值为FileStream。即= new FileStream();
  6. public:
  7. virtual char Read(int number){
  8. //额外的加密操作...
  9. stream->Read(number);
  10. }
  11. //...
  12. };
  13. class CryptoNetworkStream
  14. {
  15. Stream* stream; // = new NetworkStream();
  16. public:
  17. virtual char Read(int number){
  18. //额外的加密操作...
  19. stream->Read(number);
  20. }
  21. //...
  22. };
  23. class CryptoMemoryStream
  24. {
  25. Stream* stream; // = new MemoryStream();
  26. public:
  27. virtual char Read(int number){
  28. //额外的加密操作...
  29. stream->Read(number);
  30. }
  31. //...
  32. };

优化:抽出父类(第二个大版本)

经过上两个版本的迭代,发现CryptoFileStreamCryptoNetworkStreamCryptoMemoryStream变成一样了。只不过字段stream的具体类型需要在运行时指定。

  1. class CryptoStream
  2. : public Stream //继承Stream基类,为了让Read、Seek、Write函数满足接口规范(和stream的函数一样),成为一个虚函数
  3. {
  4. Stream* stream; //运行时再指定,编译时声明为基类指针
  5. public:
  6. CryptoStream(Stream* stm):stream(stm){
  7. }
  8. virtual char Read(int number){
  9. //额外的加密操作...
  10. stream->Read(number);//读文件流(多态调用)
  11. }
  12. virtual void Seek(int position){
  13. //额外的加密操作...
  14. stream::Seek(position);//定位文件流(多态调用)
  15. //额外的加密操作...
  16. }
  17. virtual void Write(byte data){
  18. //额外的加密操作...
  19. stream::Write(data);//写文件流(多态调用)
  20. //额外的加密操作...
  21. }
  22. };

至此,问题已经得到了极大的缓解了。以下是此次迭代的完整代码

  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. virtual ~Stream(){}
  8. };
  9. //主体类
  10. class FileStream: public Stream{
  11. public:
  12. virtual char Read(int number){
  13. //读文件流
  14. }
  15. virtual void Seek(int position){
  16. //定位文件流
  17. }
  18. virtual void Write(char data){
  19. //写文件流
  20. }
  21. };
  22. class NetworkStream :public Stream{
  23. public:
  24. virtual char Read(int number){
  25. //读网络流
  26. }
  27. virtual void Seek(int position){
  28. //定位网络流
  29. }
  30. virtual void Write(char data){
  31. //写网络流
  32. }
  33. };
  34. class MemoryStream :public Stream{
  35. public:
  36. virtual char Read(int number){
  37. //读内存流
  38. }
  39. virtual void Seek(int position){
  40. //定位内存流
  41. }
  42. virtual void Write(char data){
  43. //写内存流
  44. }
  45. };
  46. //第一种扩展操作:加密
  47. class CryptoStream: public Stream {
  48. Stream* stream;//...
  49. public:
  50. CryptoStream(Stream* stm):stream(stm){
  51. }
  52. virtual char Read(int number){
  53. //额外的加密操作...
  54. stream->Read(number);//读文件流
  55. }
  56. virtual void Seek(int position){
  57. //额外的加密操作...
  58. stream::Seek(position);//定位文件流
  59. //额外的加密操作...
  60. }
  61. virtual void Write(byte data){
  62. //额外的加密操作...
  63. stream::Write(data);//写文件流
  64. //额外的加密操作...
  65. }
  66. };
  67. //第二种扩展操作:缓存
  68. class BufferedStream : public Stream{
  69. Stream* stream;//...
  70. public:
  71. BufferedStream(Stream* stm):stream(stm){
  72. }
  73. //...
  74. };
  75. //运行时装配
  76. //1. 编译不存在加密文件流、缓存文件流、加密缓存文件流。我们没有那样的类
  77. //2. 但是在运行的时候,我们可以通过组合的方式把它们装配起来,满足我们的需求
  78. void Process(){
  79. //创建一个文件流
  80. FileStream* s1 = new FileStream();
  81. //传入s1文件流,s2为文件流的加密操作
  82. CryptoStream* s2 = new CryptoStream(s1);
  83. //传入s1文件流,s3为文件流的缓存操作
  84. BufferedStream* s3 = new BufferedStream(s1);
  85. //传入s2文件流加密操作,s4为文件流加密操作 + 缓存操作
  86. BufferedStream* s4 = new BufferedStream(s2);
  87. }

优化:再抽象出父类,即装饰器(第三个大版本)

其实不难看出,CryptoStreamBufferedStream还存在冗余代码。有着一个相同的成员变量。

如果某一个类的多个子类有着共同的字段,我们可以把这个字段往上提一层——马丁·福勒经典重构理论

Stream的子类CryptoStreamBufferedStream有着共同的字段Stream* stream,所以我们可以把它们往上再提一层

我们有两种提法。
一、第一种提法:把Stream* stream提到基类中(即Stream里)

  1. class Stream{
  2. Stream* 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. virtual ~Stream(){}
  8. };

提到父类Stream中,但这会有一个问题。主体类FileStreamNetworkStreamMemoryStream都不需要这个字段

二、第二种提法:设计一个中间类

  1. //中间的装饰类
  2. class DecoratorStream: public Stream{
  3. protected: //子类要用,申明为protected
  4. Stream* stream;//...
  5. DecoratorStream(Stream * stm):stream(stm){
  6. }
  7. };

第二种提法是可以的,其实就是我们的装饰设计模式

完整代码:

  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. virtual ~Stream(){}
  8. };
  9. //主体类
  10. class FileStream: public Stream{
  11. public:
  12. virtual char Read(int number){
  13. //读文件流
  14. }
  15. virtual void Seek(int position){
  16. //定位文件流
  17. }
  18. virtual void Write(char data){
  19. //写文件流
  20. }
  21. };
  22. class NetworkStream :public Stream{
  23. public:
  24. virtual char Read(int number){
  25. //读网络流
  26. }
  27. virtual void Seek(int position){
  28. //定位网络流
  29. }
  30. virtual void Write(char data){
  31. //写网络流
  32. }
  33. };
  34. class MemoryStream :public Stream{
  35. public:
  36. virtual char Read(int number){
  37. //读内存流
  38. }
  39. virtual void Seek(int position){
  40. //定位内存流
  41. }
  42. virtual void Write(char data){
  43. //写内存流
  44. }
  45. };
  46. //提供一个中间装饰类,为Stream的扩展操作提供一个基类
  47. DecoratorStream: public Stream{
  48. protected:
  49. Stream* stream;//...
  50. DecoratorStream(Stream * stm):stream(stm){
  51. }
  52. };
  53. //扩展操作一:加密操作
  54. class CryptoStream: public DecoratorStream {
  55. public:
  56. CryptoStream(Stream* stm):DecoratorStream(stm){
  57. }
  58. virtual char Read(int number){
  59. //额外的加密操作...
  60. stream->Read(number);//读文件流
  61. }
  62. virtual void Seek(int position){
  63. //额外的加密操作...
  64. stream::Seek(position);//定位文件流
  65. //额外的加密操作...
  66. }
  67. virtual void Write(byte data){
  68. //额外的加密操作...
  69. stream::Write(data);//写文件流
  70. //额外的加密操作...
  71. }
  72. };
  73. //扩展操作二:缓存操作
  74. class BufferedStream : public DecoratorStream{
  75. //与CryptoStream类似
  76. };
  77. void Process(){
  78. //运行时装配
  79. FileStream* s1=new FileStream(); //只需要一个文件流,没有加密、缓存的操作
  80. //加密流必须传入一个流进来,这就是扩展操作,你在谁的基础上再做操作
  81. //这就是装饰的含义,附着在其他之上的操作
  82. CryptoStream* s2=new CryptoStream(s1); //文件加密流
  83. BufferedStream* s3=new BufferedStream(s1); //文件缓存流
  84. BufferedStream* s4=new BufferedStream(s2);
  85. }

说明:装饰的含义,就是附着在本体之上的其他操作,即扩展操作。所以如果你要使用装饰的话,必须指定其本体是谁
例如:

  1. FileStream* s1=new FileStream();
  2. //只需要一个文件流,没有加密、缓存的操作
  3. //加密流(加密流是装饰类的子类),它必须传入一个流进来
  4. //这就是扩展操作,你在谁的基础上再做操作
  5. //这就是装饰的含义,附着在本体之上的操作
  6. CryptoStream* s2=new CryptoStream(s1); //文件加密流
第一个版本 第三个版本(装饰模式)
类图 image.png image.png
类规模 1+n+n*m!/2 **1+n+1+m**

为什么两个版本类规模差异如此之大?

  • 第一个版本背后有很多重复代码
  • 是对继承的不良使用,使用组合更好。用组合的方式支持未来的一种变化

**
举例:每个英雄都可以放QWER技能

动机

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

  1. 静态特质:第一个版本CryptoFileStream类中FileStream::Read(number);,这永远是调用FileStream的Read函数,是不会变化的代码。这个是由继承实现的
  2. 动态特质:在第三个版本中,CryptoStream类中的stream->Read(number)。这句代码是用多态指针来调虚函数,这句话就有变化了。这个是由组合实现的

如何使“对象功能的扩展”能够根据需要来动态地实现?
同时避免“扩展功能的增多”带来子类膨胀问题?
从而使得任何“功能扩展变化”所导致的影响降为最低?

模式定义

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

结构

image.png

要点总结

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

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

  1. Decorator类既继承Stream,又组合Stream(内部有一个Stream类型的多态指针)
  2. 如果一个类B继承与A类,又具有一个A类的指针 => 99%是Decorator设计模式 => 正常情况不会这么干,要不就只有继承,要不就只有组合,不会两个都有;两个都有的情况一般都是装饰模式
  3. 组合Steam是为了什么?将来支持调用Stream的具体实现类(如FileStream、NetworkStream、MemoryStream)
  4. 继承Stream是为了什么?把它自己也声明为一个流,为了让Read、Seek、Write函数满足接口规范(和stream的函数一样),成为一个虚函数
  5. 有时候你可能看不到人家的内部字段(看不到人家有组合)。但类B继承于A,B的构造函数里还需要传A的指针,你也能判断B内部有A,B和A也有组合关系

要点三:Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为了“装饰”的含义
image.png

  1. FileStream和Stream是继承关系,但是CryptoFileStream和FileStream是继承关系嘛?非要继承吗?
    1. 不是的,它俩(Stream->FileStream;FileStream->CryptoFileStream)都不是一个变化的方向
    2. 实际上FileStream->CryptoFileStream是扩展操作,是扩展的方向;而Stream->FileStream是主体操作
    3. 主体操作和扩展操作应该分开成两个分支进行继承

image.png