"单一职责"模式
- 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往随着需求的变化,子类急剧的膨胀,同时充斥着重复代码,这时候的关键是划分责任。
- 典型模式
- Decorator Method
- Bridge Method
动机
- 在某些情况下我们可能会"过度使用继承来扩展对象的功能",由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 如何使“对象功能的扩展”能够根据需要来实现动态地实现?同时避免“扩展功能的增多”带来子类膨胀问题?从而是的如何“功能扩展变化”导致的影响降为最低?
下面有这样一个需求:
1、实现不同流的操作,文件流,网络流,内存流
2、对流进行加密操作
3、对流进行缓冲操作
4、对流进行即加密,又缓冲操作
看看下面的设计有什么问题呢?
// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual 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(number); // 定位文件流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
FileStream::Write(number); // 写文件流
// 额外的加密操作
}
};
class CryptoNetworkStream : public NetworkStream {
public:
virtual char Read(int number) {
// 额外的加密操作
NetworkStream::Read(number); // 读网络流
// 额外的加密操作
}
virtual void Seek(int position) {
// 额外的加密操作
NetworkStream::Seek(number); // 定位网络流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
NetworkStream::Write(number); // 写网络流
// 额外的加密操作
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number) {
// 额外的加密操作
MemoryStream::Read(number); // 读内存流
// 额外的加密操作
}
virtual void Seek(int position) {
// 额外的加密操作
MemoryStream::Seek(number); // 定位内存流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
MemoryStream::Write(number); // 写内存流
// 额外的加密操作
}
};
// 扩展操作:缓冲操作
class BufferedFileStream : public FileStream {
// ...
};
class BufferedNetworkStream : public NetworkStream{
// ...
};
class BufferedMemoryStream : public MemoryStream{
// ...
};
// 扩展操作:缓冲 + 加密
class CryptoBufferedFileStream : public FileStream {
// ...
};
class CryptoBufferedNetworkStream : public NetworkStream{
// ...
};
class CryptoBufferedMemoryStream : public MemoryStream{
// ...
};
void Process() {
// 编译时装配
CryptoFileStream* fs1 = new CryptoFileStream();
BufferedFileStream *fs2 = new BufferedFileStream();
CryptoBufferedFileStream = new CryptoBufferedFileStream();
}
我们画个图来看:
从图中可以看出,如果有 n 个类继承Stream,除了组合之外每一个继承的类又有m个类继承,那么有1 + n + n*m个类,如果存在组合,那么1 + n + n * m + m的两个以上的组合数,由此可见这种设计,随着子功能的增多,类的个数将以阶乘的倍数增长,将会非常的庞大。并且存在很多相同的代码。
以上设计采用了继承的发方式,我们想到设计原则里强调组合优先于继承,我们可以顺着这种思路来重构代码:
我们对扩展操作可以进行修改,可以将
// 扩展操作:加密操作
class CryptoFileStream : public FileStream {
public:
virtual char Read(int number) {
// 额外的加密操作
FileStream::Read(number); // 读文件流
// 额外的加密操作
}
virtual void Seek(int position) {
// 额外的加密操作
FileStream::Seek(number); // 定位文件流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
FileStream::Write(number); // 写文件流
// 额外的加密操作
}
};
改成
// 扩展操作:加密操作
class CryptoStream : public Stream{
Stream* stm; // 将来=new FileStream()或NetworkStream()或MemoryStream()或......
public:
CryptoStream(Stream* stm) {
this->stm = stm;
}
virtual char Read(int number) {
// 额外的加密操作
stm->Read(number); // 读文件流
// 额外的加密操作
}
virtual void Seek(int position) {
// 额外的加密操作
stm->Seek(number); // 定位文件流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
stm->Write(number); // 写文件流
// 额外的加密操作
}
};
// 扩展操作:缓冲操作
class BufferedStream : public Stream {
Stream* stm; // 将来=new FileStream()或NetworkStream()或MemoryStream()或......
// ...
};
// 运行时装配
void Process {
FileStream* s1 = new FileStream();
CryptoStream s2 = new CryptoStream(s1); // 加密文件流
BufferedStream s3 = new BufferedStream(s1); // 缓存文件流
BufferedStream s4 = new BufferedStream(s2); // 即加密又缓存
}
这样
1、把继承,改成组合,符合组合由于继承原则
2、把组合具体的抽象(FileStream)改成组合抽象的抽象(Stream),符合依赖倒置原则,
3、这样就一个加密的类,可以通过运行时构造具体的对象,均可对文件流,网络流,内存流加密,这样就三和一了(CryptoFileStream ,CryptoNetworkStream ,CryptoMemoryStream)合成了一个CryptoStream,这样就把编译时的变化扔到了运行时,删除了代码的重复性,
4、CryptoStream 类里面是虚函数,是因为我们需要遵循流的接口规范,所以继承了Stream。
CryptoStream类和BufferedStream类还有相同的部分Stream* stm,应该把它提到基类,但是基类Stream被FileStram,NetworkStream,MemoryStream继承,而这三个类不需要Stream* stm,所以引入中间类DecoratorStream 。以下给出完整版设计
// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual 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 DecoratorStream : public Stream {
protected:
Stream* stm; // 将来=new FileStream()或NetworkStream()或MemoryStream()或......
};
class CryptoStream : public DecoratorStream {
public:
CryptoStream(Stream* stm) : DecoratorStream(stm) {;}
virtual char Read(int number) {
// 额外的加密操作
stm->Read(number); // 读文件流
// 额外的加密操作
}
virtual void Seek(int position) {
// 额外的加密操作
stm->Seek(number); // 定位文件流
// 额外的加密操作
}
virtual void Write(char data) {
// 额外的加密操作
stm->Write(number); // 写文件流
// 额外的加密操作
}
};
// 扩展操作:缓冲操作
class BufferedStream : public DecoratorStream {
// ...
};
// 运行时装配
void Process {
FileStream* s1 = new FileStream();
CryptoStream s2 = new CryptoStream(s1); // 加密文件流
BufferedStream s3 = new BufferedStream(s1); // 缓存文件流
BufferedStream s4 = new BufferedStream(s2); // 即加密又缓存
}
可以看到BufferedStream s4 = new BufferedStream(s2); // 即加密又缓存,是从加密的基础上进行缓存,可以理解为在一种装饰的基础上添加另外狱中装饰。给最终的设计画个图:
这样通过装饰的办法,数量变成了1+n+1+m,一下子类少了很多。由此可见对继承的不良使用,会造成很多重复代码。
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。
要点总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差“和”多子类衍生问题“。
- Decorator类在接口上表现为is-a Commponent的继承关系,即Decorator类继承了Commponent类所具有的接口。但在实现上又表现为has-a Commponent的组合关系,即Decorator类又使用了另外一个Commponent类。
- Decorator模式的目的并非解决”多子类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“-----是为”装饰“含义。