装饰器模式属于"单一职责"模式.
在软件组件的设计中,如果责任划分不清晰,使用继承得到的结果,往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这是代码的bad smell.
典型的单一职责模式:Decorator,Bridge.这两种模式表现出了很强的"单一职责模式的味道".本文中我们讨论Decorator模式.
动机
在某些情况下,我们可能会"过度的使用继承来扩展对象的功能",由于继承为类型引入了静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀.
如何使对象功能的扩展能够根据需要动态实现,同时避免扩展功能的增多带来的子类膨胀问题?从而使得任何功能扩展变化所带来的影响最低.
实例
考虑设计一个IO的库,主要是一些流操作.可以首先定义一个基类Stream,
//业务操作
class Stream{
public:
virtual char Read(int number)=0;
virtual void Seek(int postion)=0;
virtual void Write (char data)=0;
~Stream() { }
};
基于Stream可以定义文件流,网络流,内存流等派生类,然后重写实现基类的成员函数.
class FileStream:public Stream{
public:
virtual char Read(int number){
//文件读
}
virtual void Seek(int postion){
//定位文件
}
virtual void Write (char data){
//文件写
}
};
class NetworkStream:public Stream{
public:
virtual char Read(int number){
//网络读
}
virtual void Seek(int postion){
//定位网络流
}
virtual void Write (char data){
//网络写
}
};
class MemoryStream:public Stream{
public:
virtual char Read(int number){
//内存读
}
virtual void Seek(int postion){
//定位内存流
}
virtual void Write (char data){
//内存写
}
};
现在我们想给文件流,网络流,进行功能扩展,比如在流读写的过程中,添加额外的加密操作,这个是时候应该怎么处理?
首先我们先看用继承的的方式来解决这个问题.
首先是对文件读写进行加密,这个时候会增加3个类,分别对FileStream,NetworkStream,MemoryStream
class CryptoFileStream:public FileStream{
public:
virtual char Read(int number){
//进行加密操作
FileStream::Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
FileStream::Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
FileStream::Write(data);
//进行加密操作
}
};
class CryptoNetworkStream: public NetworkStream{
public:
virtual char Read(int number){
//进行加密操作
NetworkStream::Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
NetworkStream::Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
NetworkStream::Write(data);
//进行加密操作
}
};
class CryptoMemoryStream: public MemoryStream{
public:
virtual char Read(int number){
//进行加密操作
MemoryStream::Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
MemoryStream::Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
MemoryStream::Write(data);
//进行加密操作
}
};
可以看到,这个时候类增多,并且存在很多重复代码,这种设计不好.考虑用组合的方式,不用继承.
我们这个时候不用继承,采用组合的模式,在加密的类中增加一个成员变量.这样的话可以用一个加密的类,替换掉三个派生类,加密的文件类型取决于成员stream_的取值,可以相应的对FileStream,Network,Memory进行加密.
class CryptoStream {
private:
Stream* stream_; // =new FileStream() ,new NetWorkSteam, new MemoryStream,
public:
virtual char Read(int number){
//进行加密操作
stream_->Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
stream_->Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
stream_->Write(data);
//进行加密操作
}
};
这个时候我们需要考虑一下,CryptoStream中的成员函数还都是虚函数,这个是不合理的,因为它并不是基类,为了保持接口一致性,我们需要让他继承Stream,
class CryptoStream:public Stream {
private:
Stream* stream_; // =new FileStream() ,new NetWorkSteam, new MemoryStream,
public:
CryptoStream(Stream* stream):stream_(stream){
}
virtual char Read(int number){
//进行加密操作
stream_->Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
stream_->Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
stream_->Write(data);
//进行加密操作
}
};
这个时候就出现了一个非常奇妙的现象,也就是CryptoStream 既继承了基类,同时也有一个成员变量是基类的指针.
再进一步可以将 Stream* stream_
字段上移,定义一个Decorator
class Decorator:public Stream{
public:
Decorator(Stream* stream):stream_(stream){
}
protected:
Stream* stream_;
};
class CryptoStream:public Decorator {
public:
CryptoStream(Stream* stream):Decorator(stream){
}
virtual char Read(int number){
//进行加密操作
stream_->Read(number);
//进行加密操作
}
virtual void Seek(int postion){
//进行加密操作
stream_->Seek(postion);
//进行加密操作
}
virtual void Write (char data){
//进行加密操作
stream_->Write(data);
//进行加密操作
}
};
动态地(组合)给一个对象增加一些额外的职责,就增加功能而言,Decorator模式比生成子类(继承)更加灵活(消除重复代码,减少子类个数)
总结
通过采用组合而非继承的方式,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了使用继承带来的"灵活性差"和"多子类衍生问题".
Decorator 类在接口上变现为 is a Component 的继承关系,即Decorator类继承了Component类的所具有的接口.但是在实现上又表现为has a Component的关系,即Decorator类又使用了另外一个Component类.继承是为了接口规范,组合是为了调用具体的实现类.
Decorator 模式并非解决,多子类衍生的多继承问题,Decorator模式应用的要点在于解决,主体类在多个方向上的扩展功能.