设计模式:“单一职责“ 之 Decorator Method

本文探讨了如何通过Decorator模式避免继承导致的子类膨胀问题,以文件流、网络流和内存流为例,展示了如何通过组合而非继承实现加密和缓冲操作的动态添加。通过重构代码,降低了类的数量并增强了灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

"单一职责"模式

  • 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往随着需求的变化,子类急剧的膨胀,同时充斥着重复代码,这时候的关键是划分责任。
  • 典型模式
  • 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模式应用的要点在于解决”主体类在多个方向上的扩展功能“-----是为”装饰“含义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华衣在盛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值