装饰模式是一种“单一职责”模式。
在软件组件的设计中,如果责任划分不清晰,使用继承得到的结果,往往是随着需求的变化,子类急剧膨胀,同时充斥着大量重复的代码。这时候的关键就是划清责任。
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;
并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
装饰模式可以使 “对象功能的扩展” 能够根据需要来动态地实现,同时避免“扩展功能的增多”带来的子类膨胀问题。
模式定义
动态(组合)地给一个对象增加一些额外的职责。 就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 并且 减少子类个数)。
——《设计模式》GoF
要点总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决
“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
代码示例
举一个读写文件流、网络流的例子。
定义一个流的基类Stream,里面有read 和write两个虚方法,FileStream和NetworkStream分别继承Stream,实现各自的read和write方法。
//基类
class Stream{
public:
virtual char Read(int number)=0;
virtual void Write(char data)=0;
virtual ~Stream(){}
};
//主体类 文件流
class FileStream: public Stream{
public:
virtual char Read(int number){
//读文件流
}
virtual void Write(char data){
//写文件流
}
};
//主体类 网络流
class NetworkStream :public Stream{
public:
virtual char Read(int number){
//读网络流
}
virtual void Write(char data){
//写网络流
}
};
现在想对FileStream进行一些扩展操作,在读写的时候进行加密,缓存或者加密和缓存两种扩展。
写法(一)- 不用设计模式的常规操作
- 加密
很容易会想到要再写两个类CryptoFileStream 和 CryptoNetworkStream 分别继承FileStream和NetworkStream,然后重写read和write方法,对文件流和网络流加上加密操作。
//扩展操作 读写之前进行加密
class CryptoFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
FileStream::Read(number);//读文件流
}
virtual void Write(byte data){
//额外的加密操作...
FileStream::Write(data);//写文件流
}
};
class CryptoNetworkStream : :public NetworkStream{
public:
virtual char Read(int number){
//额外的加密操作...
NetworkStream::Read(number);//读网络流
}
virtual void Write(byte data){
//额外的加密操作...
NetworkStream::Write(data);//写网络流
}
};
- 缓存
也很容易会想到要再写两个类BufferedFileStream 和 BufferedNetworkStream 分别继承FileStream和NetworkStream,然后重写read和write方法,加上缓存操作。
class BufferedFileStream : public FileStream{
//缓存...
FileStream::Read(number);//读文件流
};
class BufferedNetworkStream : public NetworkStream{
//缓存...
NetworkStream::Read(number);//读网络流
};
- 加密和缓存
按照上面的逻辑,我们再定义两个类 CryptoBufferedFileStream 和 CryptoBufferedNetworkStream ,分别继承FileStream 和 NetworkStream,重写其read和write方法,加上缓存和加密操作。
这样一来就会产生很多重复的冗余的代码。
class CryptoBufferedFileStream :public FileStream{
public:
virtual char Read(int number){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Read(number);//读文件流
}
virtual void Write(byte data){
//额外的加密操作...
//额外的缓冲操作...
FileStream::Write(data);//写文件流
}
};
客户程序
在使用加密缓存功能的时候,要new出相应的类fs2, fs3,必须写死类型,没有多态性。
void Process(){
//编译时装配
CryptoFileStream *fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}
写法(二)- 使用装饰模式
- 加密
只需要定义一个CryptoStream类,继承Stream基类并组合一个Stream类对象指针, 就可以完成对多种流的加密操作,对什么流进行操作就在构造的时候传进去什么流。继承是为了重写read()和write()虚方法,扩展加密操作。组合是为了具有多态性,适应多种流。
class CryptoStream: public Stream {// 继承Stream基类,是为了继承下面的虚方法
private:
Stream* stream;//再组合一个Stream对象,可以在运行时决定是FileStream还是NetworkStream,具有多态性。(一般在项目中看到既继承了一个接口类又定义了一个改借口类对象组合,基本就是装饰模式)
public:
CryptoStream(Stream* stm):stream(stm){ //构造的时候只需传入要加密的流的实际类型(FileStream 或者 NetworkStream)
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流 使用多态性
}
virtual void Write(byte data){
//额外的加密操作...
stream->Write(data);//写文件流 使用多态性
//额外的加密操作...
}
};
- 缓存
只需要定义一个BufferedStream类,继承Stream基类并组合一个Stream类对象指针, 就可以完成对多种流的缓存操作,对什么流进行操作就在构造的时候传进去什么流。继承是为了重写read()和write()虚方法,扩展缓存操作。组合是为了具有多态性,适应多种流。
class BufferedStream : public Stream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):stream(stm){
}
//...
};
客户端程序
定义一个文件流基础对象
如果要对文件流进行缓存操作,定义一个BufferedStream对象,传入文件流对象。
如果要对文件流进行既加密又缓存,定义一个CryptoStream对象,传入缓存的文件流对象。
void Process(){
//运行时装配-1
FileStream *fs1 = new FileStream(); //定义一个文件流基础对象。
//要对文件流进行缓存操作,定义一个BufferedStream对象,传入文件流
BufferedStream *fs2 = new BufferedStream(fs1);//传进来一个文件流,对文件流进行缓存操作
//要对文件流进行既加密又缓存,定义一个CryptoStream对象,传入缓存的文件流。
CryptoStream *fs3 =new CryptoStream (fs2);
//运行时装配-2
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}
写法(三)- 使用装饰模式(更为严谨的写法)
观察写法(二),会发现CryptoStream和CryptoStream类中都有一个共同的对象 Stream* stream; 按照马丁·福勒的设计原则,当子类有共同的成员,则要提升到基类去。显然这里如果把Stream* stream;提升到Stream基类不合适,因为除了CryptoStream和CryptoStream继承Stream,还有FileStream和NetworkStream继承了Stream。这时候需要再另外定义一个DecoratorStream类,继承Stream, 并组合一个Stream类对象指针。
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
CryptoStream和BufferedStream 类直接继承装饰类DecoratorStream,不用再添加对象组合。DecoratorStream类相当于封装了一个基类指针。
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
class BufferedStream : public DecoratorStream {
public:
BufferedStream (Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//额外的缓存操作...
stream->Read(number);//读文件流
}
virtual void Write(byte data){
//额外的缓存操作...
stream::Write(data);//写文件流
//额外的缓存操作...
}
};
客户程序:
同(二)
void Process(){
//运行时装配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);
BufferedStream* s3=new BufferedStream(s1);
BufferedStream* s4=new BufferedStream(s2);
}