在某些情况我们可能会“过度的使用继承来拓展对象的功能”,由于继承为类型引入的静态特质,使得这种拓展方式缺乏灵活性;并且随着子类的增多(拓展功能的增多),
各个子类的组合(拓展功能的组合)会导致子类的膨胀。
如何使得“对象功能的拓展能够根据需求来动态实现”,同时避免“拓展功能的增加”导致子类的膨胀的问题? 从而使得任何“功能拓展变化”所导致的影响降为最低?
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,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) {}
// ....
}
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模式比继承方式
更加的灵活(消除重复代码 和 减少子类的个数)。
class SubClass : public BaseClass {
private:
BaseClass* base = nullptr;
};
原文:https://www.cnblogs.com/cyssmile/p/13948571.html