装饰者模式

不喜欢长篇大论的,可以直接看文章最后面的一张图和我自己实现的代码。

装饰者模式

在某些情况我们可能会“过度的使用继承来拓展对象的功能”,由于继承为类型引入的静态特质,使得这种拓展方式缺乏灵活性;并且随着子类的增多(拓展功能的增多),
各个子类的组合(拓展功能的组合)会导致子类的膨胀。
如何使得“对象功能的拓展能够根据需求来动态实现”,同时避免“拓展功能的增加”导致子类的膨胀的问题? 从而使得任何“功能拓展变化”所导致的影响降为最低?

定义

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

看看如下一段代码:

// 操作
class Stream {
public:
  virtual char Read(int number) = 0;
  virtual void Seek(int Position) = 0;
  virtual void Write(char* data, int len) = 0;
  virtual ~Stream();
};
// 主体类
class FileStream : public Stream {
public:
  virtual char Read(int number) {
    // 读文件流 
  }
  virtual void Seek(int Position) {
    // 定位文件流
  }
  virtual void Write(char* data, int len) {
   // 写文件流
  }
};

class NetworkStream : public Stream {
public:
  virtual char Read(int number) {
    // 读文件流 
  }
  virtual void Seek(int Position) {
    // 定位文件流
  }
  virtual void Write(char* data, int len) {
   // 写文件流
  }
};

class MemoryStream : public Stream {
public:
  virtual char Read(int number) {
    // 读文件流 
  }
  virtual void Seek(int Position) {
    // 定位文件流
  }
  virtual void Write(char* data, int len) {
   // 写文件流
  }
}

目前的关系如图所示

如果现在要有新的需求,比如加密和缓存,那么对上述的主体进行继承,那么代码就会写成这样。

class CryptoFileStream : public FileStream {
public:
  virtual char Read(int number) {
    // ...加密
    FileStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    FilerStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    FileStream::Write(data, len);
  }
};

class CryptoNetworkStream : public NetworkStream {
public:
  virtual char Read(int number) {
    // ...加密
    NetWorkStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    NetworkStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    NetworkStream(data, len);
  }
};

class CryptoMemoryStream : public MemoryStream {
public:
  virtual char Read(int number) {
    // ...加密
    MemoryStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    MemoryStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    MemoryStream(data, len);
  }
};

class BufferFileStream : public FileStream {
public:
  virtual char Read(int number) {
    // ...缓存
    FileStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    FilerStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    FileStream::write(data, len);
  }
};

class BufferNetworkStream : public NetworkStream {
public:
  virtual char Read(int number) {
    // ...缓存
    NetWorkStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    NetworkStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    NetworkStream::Write(data, len);
  }
};

class BufferMemoryStream : public MemoryStream {
public:
  virtual char Read(int number) {
    // ...缓存
    MemoryStream::Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    MemoryStream::Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    MemoryStream::Write(data, len);
  }
};

现在这个结构图变成了这样,

这里面除了之前的主体类,操作,还对每种主机类进行拓展,
其实想想也知道,如果后续的需求越来越多,那么类的数量将会急剧膨胀。
假设基类1个,主体类n个,拓展功能m个,那么总共的的类将有 1+n+n*m!/2,这里将拓展功能可以进行排列组合。

那么可以怎么做呢?
一个神奇的操作 “继承转组合”,我们想想也知道都是继成基类或者上层的父类。

这里我不打算一步到位,我一步一步一步的写

首先将拓展功能部分由继承转成组合,组合是为了运用到多态的特质,那么就应该使用父类的指针。那么现在代码就变成了,

class CryptoFileStream {
private:
  FileStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...加密
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    stream->write(data, len);
  }
};

class CryptoNetworkStream {
private:
  NetworkStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...加密
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    stream->Write(data, len);
  }
};

class CryptoMemoryStream {
private:
  MemoryStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...加密
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    stream->Write(data, len);
  }
};

class BufferFileStream  {
private:
 FileStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...缓存
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    stream->Write(data, len);
  }
};

class BufferNetworkStream {
private:
  NetworkStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...缓存
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    stream->Write(data, len);
  }
};

class BufferMemoryStream {
private:
  MemoryStream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...缓存
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    stream->Write(data, len);
  }
};

写到这里我们就发现,不管是CryptoFileStream,CryptoMemoryStream,还是CryptoNetworkStream其中都有个stream指针,而且
这个指针都是继承至基类Stream。那么分别写出来是没必要的,那么我们就应该转化成基类的指针。

class CryptoStream : public Stream{
private:
  Stream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...加密
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    stream->write(data, len);
  }
};

class BufferStream : public Stream {
private:
 Stream* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...缓存
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    stream->Write(data, len);
  }
};

目前结构图就变成

我们看到上图中BufferStream CryptoStream中都有包含Stream对象指指针,那么可以将这个Stream指针向上提一步

class Decorator : public Stream {
protected:
  Stream* stream = nullptr;
};
class CryptoStream : public Stream{
private:
  Decorator* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...加密
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 加密
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 加密
    stream->write(data, len);
  }
};

class BufferStream : public Stream {
private:
 Decorator* stream = nullptr;
public:
  virtual char Read(int number) {
    // ...缓存
    stream->Read(number);
  }
  virtual void Seek(int Position) {
    //... 缓存
    stream->Seek(position);
  }
  virtual void Write(char* data, int len) {
   //... 缓存
    stream->Write(data, len);
  }
};

其实也可以不用提最后的一步, 那么我们统计下一共有1+n+m个类,
注意细节:
1.主要操作就是将继承改为组合, 但是有人会发现还是有继承啊,但是这里的继承是继承基类。
这里主要是为了结构统一,比如都可以用基类的指针表示。
2.使用装饰者模式后,需要在构造函数中初始化Stream 对象指针。构造函数我省略了
大致如下

class BufferStream {
  BufferStream(Stream* stm) : stream(stm) {}
// ....
}
  1. 基类的析构函数一定要带上virtual关键字,道理大家都懂。
    具体使用
Stream* s1 = new FileStream();  // FileStream为主体类,
                                // 一般只继承Stream 不包含Stream对象指针
Stream* s2 = new BufferStream(s1);
s2->Read(number);
// free
// ......

装饰者模式一般是向者一个方向拓展的,这里说的不太准确,反正就是没有明显的分段的趋势。
其次就是有时候,改编的时候,可能基类带有一定的数据,这个数据可以单独提出来也可能不会单独提出来。
这时候基类中的方法就不能生成纯虚函数,道理也很简单,带有纯虚函数的类是没有对象的。
声明成虚函数,就要有相应的实现,不然会报错误。

undefined reference to `vtable for
undefined reference to `typeinfo for 
undefined reference to `

如果没有使用纯虚函数,建议子类重写时加上override关键字。

最后看下定义

以组合的方式动态的给一个对象增加一些额外的职责,就增加功能而言Decorator模式比继承方式
更加的灵活(消除重复代码 和 减少子类的个数)。

要点

  1. 通过采用组合而非继承的方式,Decorator模式实现了运行时动态拓展对象功能,可以根据需要拓展
    功能,避免了继承带来的“灵活性差”和“多子类衍生问题”。
    2.Decorator 在接口上表现为is-a Component的继承关系, 即是Decorator继承了基类(Component)的所有的接口,但是在实现上又为has-a Component的关系,就是还包含有Component对象的指针。
    代码上表现为
class SubClass : public BaseClass {
private:
  BaseClass* base = nullptr;
};
  1. Decorator模式的目的并非是解决“多子类衍生的多继承问题”,Decortor的应用主要是
    解决“主体类在多个方向上的拓展功能”——就是“装饰的含义”。
    这样一说我上面某处举的例子就不太合适。

最后的关键,我可能画得不太对

  1. Compotent基类一般是不变的,一般提供一些纯虚函数。
  2. 主体类可能有多个,都是继承自Compotent基类,是变化的。
  3. Decorator继承自Compotent基类,包含Compotent 基类指针,一般是不变的。
  4. ConcreteDecorator继承Decoratot基类,是变化的。

我写了份Decorator模式的代码

#include <iostream>

namespace DesignPatterns {
 
class Component {
 public:
   virtual bool Operator() = 0; 
   virtual ~Component(){}
};

class Decorator : public Component {
protected:
  Component* component = nullptr;
public:
  explicit Decorator(Component* cmt) : component(cmt) {}
  Decorator() = delete;
  virtual bool Operator() override;
};

bool Decorator::Operator() {
  std::cout << " Decorator Operator begin" << std::endl;
  component->Operator();
  std::cout << " Decorator Operator end" << std::endl;
}

class ConcreteComponentA : public Component{
 public:
   virtual bool Operator() override;
};

bool ConcreteComponentA::Operator() {
  std::cout << "ConcreteComponentA Operator" << std::endl;
}

class ConcreteDecoratorA : public Decorator {
 public:
   virtual bool Operator() override;
   ConcreteDecoratorA(Component* cmt) : Decorator(cmt) {}
};

bool ConcreteDecoratorA::Operator() {
   std::cout << "ConcreteDecoratorA Operator begin" << std::endl;
   Decorator::Operator();
   std::cout << "ConcreteDecoratorA Operator end" << std::endl;
}

}  // namespace DesignPatterns


int main(int argc, char* argv[]) {
  using namespace DesignPatterns;
  Component* component = new ConcreteComponentA();
  Decorator* decorator = new ConcreteDecoratorA(component);
  decorator->Operator();
  delete component;
  delete decorator;
  return 0;
}

运行结果

posted @ 2020-11-11 11:37  cyssmile  阅读(268)  评论(1编辑  收藏  举报