1、定义与动机
-
装饰器模式定义:动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。
-
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
-
如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降到最低?
-
装饰器模式有一个很独特的地方:不仅继承接口(is a)、还组合接口(has a);继承接口是为了重写接口实现接口的方法,组合是为了动态装入接口的其他子类,这样就可以轻松的实现功能拓展,但又不违背单一职责原则!
2、案例分析
- C++中的文件流对象并没有Java中的那么丰富,在Java语言中存在各种各样的流对象,基类有InputStream/OutputStream、Reader/Writer等等
- 同时派生出各种IO流类,通过不同的功能、作用、辅助分类可以分很多种:文件流、对象流、缓冲流、缓冲文件流、加密缓冲流、等等
- 如果代码都单纯的靠继承来实现Java庞大的IO流类库,工作量是非常大的。
- 导致工作量巨大的原因:单纯通过继承来实现,存在大量的CV代码
2.1、基础实现
- 普通的实现方式就是一直继承,代码无线重复的去设计实现
- 里氏替换原则:当继承基类或者父类的代码大量被重写时,继承失去其原本的意义,换句话说可以不需要继承。
- 这样设计会导致继承失去其原本的意义,因为大量的代码都被重写(违背里氏替换原则)
class Stream{virtual char Read(int n) = 0;virtual void Seek(int n) = 0;virtual void Write(char data) = 0;virtual ~Stream(){}
};
/** FileStream、NetWorkStream、MemoryStream、ObjectStream...*/
class FileStream: public Stream{
public:virtual char Read(int n){// 读文件流}virtual void Seek(int n){// 定位文件流}virtual void Write(char data){// 写文件流}
};class NetWorkStream: public Stream{
public:virtual char Read(int n){// 读网络流}virtual void Seek(int n){// 定位网络流}virtual void Write(char data){// 写网络流}
};// CryptoFileStream、CryptoNetWorkStream、CryptoMemoryStream、CryptoObjectStream...
class CryptoFileStream: public FileStream{
public:virtual char Read(int n){// 加密FileStream::Read(n);// 加密}virtual void Seek(int n){// 加密FileStream::Seek(n);// 加密}virtual void Write(char data){// 加密FileStream::Write(n);// 加密}
};
class CryptoNetWorkStream: public NetWorkStream{
public:virtual char Read(int n){}virtual void Seek(int n){}virtual void Write(char data){}
};// BufferedFileStream、BufferedNetWorkStream、BufferedMemoryStream、BufferedObjectStream...
class BufferedFileStream: public FileStream{// ...
};
class BufferedNetWorkStream: public NetWorkStream{// ...
};// CryptoBufferedFileStream、CryptoBufferedNetWorkStream、CryptoBufferedMemoryStream、CryptoBufferedObjectStream...
class CryptoBufferedFileStream: public BufferedFileStream{virtual char Read(int n){// 加密// 缓存BufferedFileStream::Read(n);}virtual void Seek(int n){//...}virtual void Write(char data){//...}
};
- 而这样编写到了后期持续的扩展,类的数量指数增长,代码量急剧上升
2.2、普通装饰器
- 为了消除这些冗余的代码,可以优先考虑使用组合的形式来取代靠继承实现的这些IO流类
- 基于上述代码,只要灵活使用多态和组合就可以省略掉很多冗余的代码
- CryptoStream类中组合一个Stream抽象类的指针,当构造CryptoStream类时可以通过传入FileStream、NetWorkStream对象来完成动态绑定,然后CryptoFileStream、CryptoNetWorkStream…其内部所有的方法都一样(因为只是基于不同的流类套了一层加密,而具体哪个流类通过Stream抽象类接口来动态绑定),因此可以代码去重。
- 因此这样就可以实现去重
class Stream{virtual char Read(int n) = 0;virtual void Seek(int n) = 0;virtual void Write(char data) = 0;virtual ~Stream(){}
};
/** FileStream、NetWorkStream、MemoryStream、ObjectStream...*/
class FileStream: public Stream{
public:virtual char Read(int n){// 读文件流}virtual void Seek(int n){// 定位文件流}virtual void Write(char data){// 写文件流}
};class NetWorkStream: public Stream{
public:virtual char Read(int n){// 读网络流}virtual void Seek(int n){// 定位网络流}virtual void Write(char data){// 写网络流}
};// CryptoFileStream、CryptoNetWorkStream、CryptoMemoryStream、CryptoObjectStream...
class CryptoStream: public Stream{
private:Stream *stream; // new FileStream()、new NetWorkStream().....
public:CryptoStream(Stream *_stream): stream(_stream){}virtual char Read(int n){// 加密stream->Read(n);// 加密}virtual void Seek(int n){// 加密stream->Read(n);// 加密}virtual void Write(char data){// 加密stream->Read(n);// 加密}
};// BufferedFileStream、BufferedNetWorkStream、BufferedMemoryStream、BufferedObjectStream...
class BufferedStream: public Stream{Stream *stream;// ...
};// CryptoBufferedFileStream、CryptoBufferedNetWorkStream、CryptoBufferedMemoryStream、CryptoBufferedObjectStream...
class CryptoBufferedStream: public Stream{Stream *stream; //....virtual char Read(int n){// 加密// 缓存}virtual void Seek(int n){//...}virtual void Write(char data){//...}
};void process()
{// 运行时装配FileStream fileStream = new FileStream();CryptoStream cryptoStream1 = new CryptoStream(new FileStream());CryptoStream cryptoStream2 = new CryptoStream(new NetWorkStream());.... CryptoBufferedStream cryptoBufferedStream = new CryptoBufferedStream(new FileStream());
}
2.3、抽象装饰器
- 抽象装饰器就是在实现类和基类之间套了一个装饰器类
- 装饰器类负责继承基类,通过多态的动态绑定来运行时确定加载某个类
- 实现类通过继承装饰器类,调用装饰器类中的对象完成操作
class Stream{virtual char Read(int n) = 0;virtual void Seek(int n) = 0;virtual void Write(char data) = 0;virtual ~Stream(){}
};
/** FileStream、NetWorkStream、MemoryStream、ObjectStream...*/
class FileStream: public Stream{
public:virtual char Read(int n){// 读文件流}virtual void Seek(int n){// 定位文件流}virtual void Write(char data){// 写文件流}
};class NetWorkStream: public Stream{
public:virtual char Read(int n){// 读网络流}virtual void Seek(int n){// 定位网络流}virtual void Write(char data){// 写网络流}
};class DecoratorStream: Stream{
protected:Stream *stream;DecoratorStream(Stream *_stream): stream(_stream){}
};// CryptoFileStream、CryptoNetWorkStream、CryptoMemoryStream、CryptoObjectStream...
class CryptoStream: public DecoratorStream{
public:CryptoStream(Stream *_stream): DecoratorStream(_stream){}virtual char Read(int n){// 加密stream->Read(n);// 加密}virtual void Seek(int n){// 加密stream->Read(n);// 加密}virtual void Write(char data){// 加密stream->Read(n);// 加密}
};// BufferedFileStream、BufferedNetWorkStream、BufferedMemoryStream、BufferedObjectStream...
class BufferedStream: public DecoratorStream{// ...
};// CryptoBufferedFileStream、CryptoBufferedNetWorkStream、CryptoBufferedMemoryStream、CryptoBufferedObjectStream...
class CryptoBufferedStream: public DecoratorStream{virtual char Read(int n){// 加密// 缓存}virtual void Seek(int n){//...}virtual void Write(char data){//...}
};void process()
{// 运行时装配FileStream fileStream = new FileStream();CryptoStream cryptoStream1 = new CryptoStream(new FileStream());CryptoStream cryptoStream2 = new CryptoStream(new NetWorkStream());....CryptoBufferedStream cryptoBufferedStream = new CryptoBufferedStream(new FileStream());
}
4、总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所有具有的接口;但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component实现类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”—是为“装饰”的含义