Decorator-装饰模式
装饰模式用于处理,软件组件设计中的责任划分不清晰,导致子类通过继承得到大量的冗余重复代码,代码的急剧膨胀问题。装饰模式关键在于划清责任。
考虑设计一个IO库、相关流操作。其中有文件流,网络流、内存流等,操作有对流的加密,缓存等。
考虑设计一个Stream抽象基类,FileStream、NetWorkStream、MemoryStream继承与Stream基类。
而加密操作需要加密具体的流,而不是抽象上的流,所以继承自FileStream、NetWorkStream、MemoryStream。
具体代码如下:
版本1
//业务操作
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(byte 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(byte 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(byte data){
//额外的加密操作...MemoryStream::Write(data);//写内存流//额外的加密操作...}
};class BufferedFileStream : public FileStream{
//...
};class BufferedNetworkStream : public NetworkStream{
//...
};class BufferedMemoryStream : public MemoryStream{
//...
}class CryptoBufferedFileStream :public FileStream{
public:virtual char Read(int number){
//额外的加密操作...//额外的缓冲操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){
//额外的加密操作...//额外的缓冲操作...FileStream::Seek(position);//定位文件流//额外的加密操作...//额外的缓冲操作...}virtual void Write(byte data){
//额外的加密操作...//额外的缓冲操作...FileStream::Write(data);//写文件流//额外的加密操作...//额外的缓冲操作...}
};void Process(){
//编译时装配CryptoFileStream *fs1 = new CryptoFileStream();BufferedFileStream *fs2 = new BufferedFileStream();CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();}
继承图谱
我们可以发现,加密、缓冲操作是一样的,只是加密、缓冲的对象不同,但加密的对象都继承自一个共同的抽象基类。代码大量重复,可以进行代码重构。
各类扩展操作从继承各类流,改为包含各类流的基类指针Stream*。使用组合替代继承,将原先写死的FileStream::Read等改为基类指针调用Stream->Read。
Stream实质是一个多态指针。当变量的声明都为某个类型的子类时,可以直接提升声明为该变量类型。
编译时复用,将变化需求提升至运行时。
同时为了Read等接口函数的虚性质,维护接口的规范,需要继承Stream。
版本2
//业务操作
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 CryptoStream: public Stream {
Stream* stream;//...public:CryptoStream(Stream* stm):stream(stm){
}virtual char Read(int number){
//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){
//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){
//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public Stream{
Stream* stream;//...public:BufferedStream(Stream* stm):stream(stm){
}//...
};void Process(){
//运行时装配FileStream* s1=new FileStream();CryptoStream* s2=new CryptoStream(s1);BufferedStream* s3=new BufferedStream(s1);BufferedStream* s4=new BufferedStream(s2);}
最后使用时,从原先的编译时装配,改为运行时装配。
编译时,不存在流的加密缓存等类,运行时通过多态指针将各种操作组合起来,来满足未来的需求。
但到此为止,还不是最优的设计,根据马丁福勒的重构理论,如果某个类的多个子类都有同样的字段,应该将字段上提。
字段上提可以提升至基类,也可以提升至中间类。
考虑到FileStream、NetWorkStream等并不需要该字段,所以设计中间类包含字段,并继承Stream。
版本3
//业务操作
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){
//写内存流}};//扩展操作DecoratorStream: public Stream{
protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){
}};class CryptoStream: public DecoratorStream {
public:CryptoStream(Stream* stm):DecoratorStream(stm){
}virtual char Read(int number){
//额外的加密操作...stream->Read(number);//读文件流}virtual void Seek(int position){
//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){
//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public DecoratorStream{
Stream* stream;//...public:BufferedStream(Stream* stm):DecoratorStream(stm){
}//...
};void Process(){
//运行时装配FileStream* s1=new FileStream();CryptoStream* s2=new CryptoStream(s1);BufferedStream* s3=new BufferedStream(s1);BufferedStream* s4=new BufferedStream(s2);}
DecoratorStream类是中间类,包含重复字段,扩展继承该类,同时调用DecoratorStream里的构造器构造(基类构造器)。
继承图谱
可以从继承图谱看出,类的规模显著的减少,将大量的流扩展操作与各类流分离,避免了对继承的不良使用,提升代码的复用性。
装饰模式的要点在于,组合基类并继承基类。从经验推断,如果某类继承并组合某个基类,99%的可能是使用了装饰模式。
装饰模式是为了解决因为继承的误用,随着子类的增多,在静态特质下,各种子类的组合会导致更多的子类急剧的膨胀。通过动态的给对象增加格外的职责,来消除重复代码,减少子类个数。