C++设计模式:单一职责划分—修饰模式

在写一个面向对象的程序时,如果程序要扩展功能,使用继承的方法,会导致代码量急剧膨胀。
例如:下面的例子就是写一个文件分割器,基类是stream类,而流可能是文件、网络、内存的流,然后在文件、网络、内存的流中,对于基类中的三个纯虚函数进行重写。每扩展一个功能,就要写针对FileStream,NetworkStream,MemoryStream这三个主体类中的所有功能函数进行编写。

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) {
		//写文件流
	}
};
//扩展操作I,对于文件流的加密
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);
	}
};
//扩展操作I,对于网络流的加密
class CryptoNetworkStream : public NetworkStream {
public:
	virtual char Read(int number) {
		//加密操作
		NetworkStream::Read;
	}
	virtual void Seek(int position) {
		//加密操作
		NetworkStream::Seek(position);
	}
	virtual void Write(char data)
	{
		//加密操作
		NetworkStream::Write;
	}
};
//扩展操作I,内存流进行加密操作,略写
class CryptoMemoryStream : public MemoryStream {
public:
	virtual char Read(int number) {
		MemoryStream::Read(number);
	}
	//......
};
//扩展操作II,对文件流既加密又缓冲
class CryptoBufferedFileStream :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);
	}
};
//扩展操作II,对网络流
class CryptoBufferedNetworkStream:public NetworkStream{
	//Read
	//Seek
	//Write
};
//扩展操作II,对内存流
class CryptoBufferedMemoryStream:publicMemoryStream {
	//Read
	//Seek
	//Write
};

可以看出,代码量十分的庞大,这对于一个软件的可维护性是有着致命打击的。优化方式,就是使用组合的方式取代继承,使用动态的方式取代静态的方式:
即:在运行的时候,用多态的方式,对子类进行变化。取代在编译的时候的变化。
具体实现:例子中有三种情况,分别是FileStream,NetworkStream和MemoryStream,这三种情况是在编译时就规定好的,所以代码量很大,复用性不好。
首先,对于继承的方式,改为组合的方式:

class CryptoFileStreampublic stream{
public:
virtual char Read(int number){stream::Read(number);}
//..下同
}
//修改为
class CryptoFileStream{
stream* str;
public:
virtual char Read(int number){str->Read(number);}
//..下同
}

通过在子类中首先生成一个父类对象,然后通过父类对象调用函数。这与继承父类,然后通过类名作用域调用函数的功能相同。

然后就不难发现,CryptoFileStream,CryptoNetworkStream,CryptoMemoryStream,这三个类几乎一模一样。

class CryptoNetwordStream{
stream* str;
public:
virtual char Read(int number){str->Read(number);}
//..下同
}
class CryptoMemoryStream{
stream* str;
public:
virtual char Read(int number){str->Read(number);}
//..下同
}

写到这里,就不难想到,在调用的时候通过多态性来消除重复性,从而把这三类的流归并为一个类。在调用的时候,通过判断右侧new出来的类是哪个类型的(FileStream,NetworkStream,MemoryStream),来对应调用相应的虚函数。
但是!此时类中,仍然重写了虚函数,重写的是谁的虚函数?当然还是父类(基类),因此这里要重新继承回父类,这是为了更好地定义接口,便于使用多态性。
当然,要加一个构造函数。

class CryptoStream:public stream
{stream* stream;
public:
//使用初始化列表的构造函数
CryptoStream(stream* stream):stream(stream){
}
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:public stream{
stream* stream;
public:
BufferedStream(stream* stream):stream(stream){
}
//下同
}

重写完成,可以发现,原本三个类变成了一个类。
而调用方式也相应的改变:
原本为编译时装配

CryptoBufferedFileStream* s1 = new CryptoBufferedFileStream();
CryptoBufferedMemoryStream* s2 = new CryptoBufferedMemoryStream();

现在为运行时装配

FileStream* s1=new FileStream();
//加密s2
CryptoStream* s2=new CryptoStream(s1);
//缓冲s3
BufferedStream* s3=new BufferedStream(s1);
//既加密又缓冲s4
BufferedStream* s4=new BufferedStream(s2);

附加题:

我以为这就全部优化完成了,但是仍然没有。根据马丁福乐的重构理论:
如果多个子类有同样的字段的话,理论上应该把这个字段往上层(父类)提一提。
比如stream* stream这个所有子类都有的字段,假如我们把它上移到基类stream中。

class stream
{stream* stream;
public:
	virtual char Read(int number)= 0;
	virtual void Seek(int position)= 0;
	virtual void Write(char data)= 0;
	virtual ~stream(){}
};

可以发现,主体类中,FileStream、NetworkStream、MemoryStream不需要这个字段。
因此应该设计一个中间装饰类,这个装饰类比起基类只多一个字段。

DecoratorStream:public stream
{protected:
stream* stream;
DecoratorStream(stream* stream):stream(stream){}}

因此其他部分也应该做对应改动:

class CryptoStream:public DecoratorStream
{
public:
//使用初始化列表的构造函数
CryptoStream(stream* stream):DecoratorStream(stream){
}
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同

总结:
在某些情况下,我们可能会“过度使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活行,并且随着子类的增多(扩展功能的增多),各类子类的组合,使代码量急剧膨胀。
因此可以使用一种灵活的方式,动态地扩展功能。其核心是组合而非继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值