C++设计模式学习心得(五)——装饰器模式

装饰器模式的运用场景:
我们首先来看一个栗子:

// 流IO类
class CStream
{
public:
	// 读取流
	virtual void read();
	
	// 定位流
	virtual void seek();

	// 写入流
	virtual void write();
}

在CStream这个类中一共有三个抽象的接口:读取流、定位流、写入流。该类主要是用于处理流式IO,在实际的使用中,我们通常遇到流操作为硬盘文件流、网络流、内存流。首先理解这三种流其实是流基类的子类(Is-A)。所以这时候这三个类应该继承自CStream。
接下来,我们需要给流处理类增加一些需求,比如在流读入读出时对数据进行加解密,或者对流数据进行缓存。例如新增需求是需要一个支持对流数据加解密和支持缓存功能的硬盘文件流读取类(CCrytoBufferFileStream),同时也会有需求只支持缓存的文件输入输出类(CBufferFileStream),一般来说开发者实现时都会将CCrytoBufferFileStream类和CBufferFileStream看作是CStream类的子类,然后重写三个接口函数,在重写的三个接口里面实现加解密和缓存等功能。这样的话相当于多个扩展功能之间组合,再与流类型(文件流,网络流,内存流)之间相互组合,我们将必须实现非常多的子类,那代码将会非常的膨胀。

OK,show me the code!

// 支持加解密和缓存功能的文件流类
Class CCrytoBufferFileStream : public CFileStream
{
	public:
		virtual void read()
		{
			// 读取文件流
			std::string strStream = CFileStream::read();
	
			// 解密文件流
			crytoStream(strStream);
	
		    // 缓存文件流
		    buffer(strStream);
		}
}

为了节省时间,我们只实现了read方法的伪代码,但是seek和write方法的实现步骤也是一样的,同时,如果我们需要支持加密和支持缓存的网络流处理类,那么其代码应该如下:

Class CCrytoBufferNetStream : public CNetStream
{
	public:
		virtual void read()
		{
			// 读取Net流
			std::string strStream = CNetStream::read();
	
			// 解密文件流
			crytoStream(strStream);
	
		    // 缓存文件流
		    buffer(strStream);
		}
}

可以看出CCrytoBufferNetStream类和CCrytoBufferFileStream类中的实现其实有冗余代码,结合上文所说,如果所有功能扩展类都用继承的方式来进行扩展,那么随着业务需求的不断增加,我们必将在工程中增加非常多的类似冗余代码。
CCrytoBufferNetStream类和CCrytoBufferFileStream类中唯一的区别其实只有真正读取流时时调用的静态方式的实现类不一样,其余都是冗余代码。设计模式的一个重要原则就是复用
解决方案:面向对象设计八大原则又登场了:单一职责原则,那么对于流IO这一抽象,其实只有IO才是其真正的主要职责,也就是硬盘IO、NetIO、MemoryIO这一层级的功能分类都是其主要职责的变化,该类扩展用继承的方法来实现无可厚非。但是加解密和缓存都是其扩展的职责,OO设计八大原则再次闪亮登场:优先使用组合而不是继承来对类的功能进行扩展,所以我们的解决方案就呼之欲出了,请看下面的代码

Class CCrytoStream : public CStream
{
    CStream* m_pStream;
	public:
		CCrytoStream::CCrytoStream(CStream* pStream)
			: CStream(),
			m_pStream(pStream)
		{
		}
	
    public:
		virtual void read()
		{
			// 读取Net流
			if(nullptr == m_pStream)
			{
				return;
			)
			std::string strStream = m_pStream->read();
	
			// 解密文件流
			crytoStream(strStream);
		}
}

在新实现的加密类中我们运用多态性,在加解密类CCrytoStream中增加一个私有变量,这是一个CStream类的指针,该指针用于保存实际用于实现IO的类,而新类的职责仅仅只是对调用IO类读取的结果进行加解密,完全符合单一职责原则,同时使用组合来实现了对流IO这一基础类的加解密功能扩展,简直完美。以此类推,缓存功能也是一样的做法。

下面我们来看一下新类和旧类的用法区别:

{
	// 旧的支持加解密和缓存的网络流处理类的用法;
	CCrytoBufferNetStream crytoBufferNetStream;
	crytoBufferNetStream.read();
	
	// 新设计的类用法
	CNetStream* pNetStream = new pNetStream;
	CCrytoStream* pCrytoNetStream = new CCrytoStream(pNetStream);
    CBufferStream* pCrytoBufferNetStream = new CBufferStream(pCrytoNetStream);
    pCrytoBufferNetStream->read();
}

看到这里,我们已经运用适配器模式完美的重构一块代码,效果真的棒,几百行的代码瞬间优化成了几十行,本来需要实现很多很多类,现在只需要这么几个类,这里有一个很关键的概念:运行时装配,仔细看新设计的用法,我们是在运行时将各个类的指针一层一层传入后续的扩展类中,然后使用多态性来实现支持加解密和缓存的网络流功能的,这就是说实际上我们使用的这个类是在运行时才装配出来的,而反观老的用法,实际上是一种编译时装配,高下立判,尤其是当你在嵌入式开发等对资源稀缺的环境下开发时。
别激动,精益求精,到这里可还有东西可挖掘。我们再来看CBufferStream类的代码:

Class CBufferStream : public CStream
{
    CStream* m_pStream;
	public:
		CBufferStream::CBufferStream(CStream* pStream)
			: CStream(),
			m_pStream(pStream)
		{
		}
	
    public:
		virtual void read()
		{
			// 读取Net流
			if(nullptr == m_pStream)
			{
				return;
			)
			std::string strStream = m_pStream->read();
	
			// 解密文件流
			bufferStream(strStream);
		}
}

CCrytoStream类的代码比较,除了bufferStream()和crytoStream()这两个类的实际业务接口以外,其实其他的东西都是一样的,根据《重构》一书中的经典论断:两个同一层级的类中出现相同的字段及其操作时,就应该将其提升到父类中,这里的CStream指针以及其所引入的实际IO接口其实就是可以提升的代码。但是我们将其提升到CStream中明显是不合适的,因为CFileStream等类也是继承自CStream,但是他们并不需要这一字段。所以我们应该再封装一层,那就是CDecratorStream类,该类中实现了初始化CStream指针并调用其进行实际的IO操作,同时留下扩展功能的接口,而CBufferStream类和CCrytoStream则只是继承于它,然后实现各自的业务扩展接口。
最后总结:装饰器模式的关键其实就是将一个类的主要职责和扩展职责分清楚,扩展职责则用组合的方式来进行实现(主职责类+扩展类),同时,分层的思想也体现了OO设计八大原则之首:依赖倒置原则,装饰器模式其实用CDecratorStream就对其进行了封装,也就是依赖倒置原则中的那个抽象,对于所有的扩展功能类,他们都是低层模块,高层模块则是CStream类的另一个子类分支(CFileStream等对流IO主要职责进行封装的类),同时可能也会有人想到,为什么CDecratorStream还需要继承CStream类呢,有了组合就已经足够实现其功能了,但是这里继承就是为了统一接口调用,精髓就是:使用上或外观上看起来是IS-A关系,然而内部实现确实Has-A关系,诸位看官细细品味。。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值