如何使“对象功能的扩展”能够根据需要来动态的实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
我们在软件构建的过程中,可能会遇到很多针对“流”的操作。比如文件流,网络流等等。很自然的,我们将“流”这一概念抽象,形成一个基类,我们可能得到如下的代码
//流的操作
class Stream{
public:
virtual string Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(string data) = 0;
virtual ~Stream(){}
};
//功能实现的主体类
//文件流
class FileStream : public Stream{
private:
//...
public:
virtual string Read(int number){
//读取某个编号的文件流
}
virtual void Seek(int position){
//定位某个编号的文件流
}
virtual void Write(string data){
//写文件流
}
};
//网络流
class NetworkStream : public Stream{
private:
//...
public:
virtual string Read(int number){
//读取某个网络流
}
virtual void Seek(int position){
//定位某个网络流
}
virtual void Write(string data){
//写网络流
}
};
很多情况下,我们需要对这些类进行一些扩展操作,比如对其进行加密,有可能的操作如下:
class CryptoFileStream : public FileStream{
public:
virtual string Read(int number){
//解密操作
FileStream::Read(number);
}
virtual string Seek(int position){
//加密操作
FileStream::Seek(position);
//加密操作
}
virtual string Write(string data){
//加密操作
FileStream::Write(data);
//加密操作
}
};
class CryptoNetworkStream : public NetwordStream{
public:
virtual string Read(int number){
//解密操作
NetwordStream::Read(number);
}
virtual string Seek(int position){
//加密操作
NetwordStream::Seek(position);
//加密操作
}
virtual string Write(string data){
//加密操作
NetwordStream::Write(data);
//加密操作
}
};
不仅如此,我们还有很大可能有其他需求,例如,对一个流进行缓冲(buffer),对一个流进行部分解密并且转发。。等等
我们观察上面图中的情况,在需求增加后,类的个数由于一昧的继承关系,会有爆炸式的增长。再结合代码分析,如果我们使用同一种对数据的加密方式,我们在CryptoFileStream 类和CryptoNetworkStream 类中,写了高度重复的代码,也就是对数据的加密过程。
C++设计模式#1-CSDN博客我们的设计原则中有一条,叫做优先使用对象组合,而不是类继承。
我们思考一下,FileStream/NetworkStream是确实继承于Stream的。但是,CryptoNetworkStream类是必须继承NetworkStream类的吗?这其实是一个功能上的扩展,而不是一个真正的非继承不可的关系。
class CryptoStream : public Stream{
private:
Stream* m_stream;
public:
CryptoStream(Stream* stream) : m_stream(stream) {}
virtual string Read(int number){
//解密操作
m_stream->Read(number);
}
virtual string Seek(int position){
//加密操作
m_stream->Seek(position);
//加密操作
}
virtual string Write(string data){
//加密操作
m_stream->Write(data);
//加密操作
}
};
int main(){
FileStream* f1 = new FileStream();
NetworkStream* n1 = new NetworkStream();
CryptoStream cs1(f1);
CryptoStream cs2(n1);
cs1.Read(number);
cs2.Write(data);
//...
}
我们观察CryptoStream 类,不再从FileStream或其他流的子类中继承,而是将其作为一种对象组合。在运行时对其进行装配,充分的利用了多态的技术,避免了为每一个流的子类都重写一个加密的子类这种冗余的情况。
其中,从Stream类继承是因为要保证read,seek,write三种方法的接口规范。另外,如果有其他例如缓冲的需求,可以用同样的方式实现。
我们提到了,如果有一个缓冲的需求,我们可以再用同样的方法实现。例如实现一个BufferStream 类。那么,我们在脑海中构建一个这样的BufferStream类,我们发现,这个类也是从Stream类中继承而来,同样也有一个Stream*的指针成员变量。
在设计原则中,如果一个类的多个子类有相同的结构,那么应该将这些相同的部分向上提。
那么,试想一下,如果我们将Stream*这个指针提到Stream类中是否合适。很明显,这是不合适的,因为FileStream也是从Stream中继承的,但是明显它不需要这样一个指针成员。
所以,应该设计一个中间类,也就是Decorator
class DecoratorStream : public Stream{
protected:
Stream* stream;
public:
DecoratorStream(Stream* stm) : stream(stm) {}
};
class CryptoStream : public DecoratorStream{
public:
CryptoStream(Stream* stream) : DecoratorStream(stream) {}
virtual string Read(int number){
//解密操作
stream->Read(number);
}
virtual string Seek(int position){
//加密操作
stream->Seek(position);
//加密操作
}
virtual string Write(string data){
//加密操作
stream->Write(data);
//加密操作
}
};
如果有bufferstream类可以用同样的方式实现。
现在我们的类的规模变成了上图中的形式。我们成功的将一种乘法的关系变成了加法的关系。避免了这种爆炸式的类规模的增长。我们用一种“组合”的方式,解构了继承的关系。
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,装饰模式比生成子类(继承)更为灵活(消除重复代码,并且减少子类个数)。——《设计模式》GoF
Component -> Stream
Decorator -> DecoratorStream
ConcreteComponent -> FileStream/NetworkStream
ConcreteDecoratorA/B -> CryptoStream/BufferStream