一、模式定义
分组到单一职责组,这个模式的讲解过程中提到,这是组合的应用。继承太多导致子类膨胀,重构的方向是划清责任,其后组合在一起。
动态(组合)地给一个对象增加额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。
1、原有代码
当类的继承过多之后,代码好臃肿。按照下面的原始版本设计,是有问题的,最终类的个数可能是1 + n + n * (m! / 2)
。并且,子类中的许多代码是重复的。
arduino
class Stream {
public:
virtual char read(int number) = 0;
virtual void seek(int position) = 0;
virtual void write(char data) = 0;
virtual ~Stream() {}
};
// 主体部分
class FileStream : public Stream {
public:
virtual char read(int number) {
// 读文件流
}
virtual void seek(int position) {
// 定位文件流
}
virtual void write(char data) {
// 写文件流
}
};
class NetworkStream : public Stream {
public:
virtual char read(int number) {
// 读网络流
}
virtual void seek(int position) {
// 定位网络流
}
virtual void write(char data) {
// 写网络流
}
};
class MemoryStream : public Stream {
public:
virtual char read(int number) {
// 读内存流
}
virtual void seek(int position) {
// 定位内存流
}
virtual void write(char data) {
// 写内存流
}
};
// 扩展操作
// 需要对每一种流做加密操作,加密操作时一样的,只是流不一样
// 此处基于所有的流继承出新的字类,有许多重复的加密代码
class CryptoFileStream : public FileStream {
public:
virtual char read(int number) {
// 额外的加密操作...
FileStream::read(number);
}
virtual void seek(int position) {
// 额外的加密操作...
FileStream::seek(position);
// 额外的加密操作...
}
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 position) {
// 额外的加密操作...
NetworkStream::seek(position);
// 额外的加密操作...
}
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 position) {
// 额外的加密操作...
MemoryStream::seek(position);
// 额外的加密操作...
}
virtual void write(char data) {
// 额外的加密操作...
MemoryStream::write(data);
// 额外的加密操作...
}
};
// 需要对流进行缓冲,缓冲和加密一样理解,都会有重复内容
class BufferedFileStream : public FileStream {
// ...
};
class BufferedNetworkStream : public NetworkStream {
// ...
};
class BufferedMemoryStream : public MemoryStream {
// ...
};
// 加密缓冲同时有,当然是可以继承CryptoFileStream或者BufferedFileStream的
class CryptoBufferedFileStream : public FileStream {
public:
virtual void read(int number) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::read(number);
}
virtual void seek(int position) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::seek(position);
// 额外的加密操作...
// 额外的缓冲操作...
}
virtual void write(char data) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::write(data);
// 额外的加密操作...
// 额外的缓冲操作...
}
};
此版本有两个问题:一是类的数量太多,二是重复代码太多。
2、重构版本
先将3个扩展基础类改为1个。
每一个Ctypto*Stream
,都调用的基类方法,3个Ctypto类,调用3个不同的基类方法,它们的3个基类继承于同一个基类Stream
,于是可以使用多态,在代码上,把调用基类,改为对一个Stream*
成员的调用。
既然将对基类FileStream
的read
调用改为类成员stream
调用,那么3个添加了加密操作的类,代码上是一样的,具体差异则体现在stream
多态调用上面,所以3个类可以合为一个类,直接继承Stream
。
其实继承Stream
和有一个stream
的成员,是互不干扰的两回事。
继承Stream,定义了子类的接口规范(这是老师的原话)。
stream
成员,多态的运用。设计模式真谛:编译时一样,使用多态,在运行时弄出不一样。其实就是为CryptoStream的stream赋值的时候,直接new FileStream。
arduino
class CryptoStream : public Stream {
// 运行时,变为不同的FileStream、NetworkStream或者MemoryStream
Stream* stream;
public:
CtyptoStream(Stream *stm) : stream(stm) {}
virtual char read(int number) {
// 额外的加密操作...
stream->read(number);
}
virtual void seek(int position) {
// 额外的加密操作...
stream->seek(position);
// 额外的加密操作...
}
virtual void write(char data) {
// 额外的加密操作...
stream->write(data);
// 额外的加密操作...
}
};
// 同理可以有BufferedStream设定
class BufferedStream : public Stream {
Stream* stream;
public:
BufferedStream(Stream *stm) : stream(stm) {}
// ...
};
3、最终版本
当有以上的写法之后,还可以稍微重构下,如果两个子类都有相同的共同变量,那么应该将这些共有变量提取到父类中,是为装饰模式。
arduino
DecoratorStream : public Stream {
protected:
Stream* stream;
DecoratorStream(Stream *stm) : stream(stm) {}
};
class CtyptoStream : public DecoratorStream {
public:
CtyptoStream(Stream *stm) : DecoratorStream(stm) {}
// ...
};
class BufferedStream : public DecoratorStream {
public:
BufferedStream(Stream *stm) : DecoratorStream(stm) {}
// ...
};
4、两种不同的调用方式
scss
void process() {
// 编译时装配, 未改进版本的使用
CryptoFileStream* fs1 = new CryptoFileStream();
BufferedFileStream* fs2 = new BufferedFileStream();
CryptoBufferedFileStream* fs3 = new CryptoBufferedFileStream();
// 运行时装配,改进版本的使用
FileStream* s1 = new FileStream();
CryptoStream* s2 = new CryptoStream(s1);
BufferedStream* s3 = new BufferedStream(s1);
// 即加密,又缓冲. 这里的执行,以调用read举例
// s4->read(n), 首先会执行一段BufferedStream的'额外缓冲操作'
// 然后执行s2的加密操作
// 这里的组合,归根结底,就是各种函数的组合?只是在代码上美观很多?
BufferedStream* s4 = new BufferedStream(s2);
}
二、项目应用
项目中使用较少。
有想一想Python的装饰器和这个模式的关系。
有一点点关系:它们都是为了在不改变原有代码基础上做些新的事情。
但区别大大的,Python的装饰器是语言语法层面上的内容,而装饰模式是设计层面的,与语言无关。
三、其他杂谈
1、感觉这个模式对现在的我来说,已经有深度了,需要思考,且项目中似乎没用到。但是再次被强调,模式都是通过代码重构得出的。
2、其实我现在(2020-7-9)有点疑惑的是,最终的版本真的能够达到目的么?心中知道是的,因为是一步一步跟着重构过来的。但重构前后的变化,是真的大啊!重新理一遍之后,理解多了一些,但是没有实际的使用经验,没有以前3个模式的融会贯通。
3、四年后再看本模式,相较四年前变化并不多,因为工作中该模式使用的依然很少。不过我知道的是,重复代码太多,是肯定不行的,当看见重复代码很多时,就需要重构了。
另外,此篇博客讲得很好:refactoringguru.cn/design-patt...