Decorator装饰模式

Decorator装饰模式



前言

关于装饰模式我有以下几个问题想问:

  • 什么是装饰模式?
  • 为什么需要装饰模式(也就是什么情况下使用装饰模式)?
  • 装饰模式的优点是什么?
  • 装饰模式的缺点是什么?

我想通过回答上面四个问题,揭开装饰模式的面纱。

模式定义

在《设计模式》一书中,对装饰模式做了简洁而清晰的释义:

动态地给一个对象增加一些额外的职责。就功能而言,Decorator模式比继承更为灵活。

GoF既然用其跟继承做比较,说明这样的比较一定存在什么意义。

与继承比较

假设一个场景,我们需要设计Stream这样一个抽象基类,它包括了读写、定位操作:

class Stream {
public:
	virtual void read()=0;
	virtual void write()=0;
	virtual void seek()=0;
	
	~Stream() {}
};

对现实中的流来说,存在符合的事物有很多,比如文件流,网络流,内存流……这些对“流”的操作都有读写、定位,也就是对抽象基类Stream实现。因此对它们的设计最好方式是继承自Stream:

class FileStream: public Stream {
public:
	// read,write,seek都是对文件流操作
	virtual void read() {...}
	virtual void write() {...}
	virtual void seek() {...}
};

class NetworkStream: public Stream {
public:
	// read,write,seek都是对网络流操作
	virtual void read() {...}
	virtual void write() {...}
	virtual void seek() {...}
}class MemoryStream: public Stream {
public:
	// read,write,seek都是对内存流操作
	virtual void read() {...}
	virtual void write() {...}
	virtual void seek() {...}
}

由于FileStream,NetworkStream,MemoryStream分别对文件流,网络流,内存流操作,因此它们读写、定位操作都是不同的,所以不能在Stream中统一实现,需要在各自的类中定义。

现在,如果业务需求,需要对流做额外的如加密、缓存等功能,倘若沿用继承的方式,代码会成如下这样:

class CryptoFileStream: public FileStream {
public:
	virtual void read() {
		FileStream::read();
		// 解密操作
	}
	virtual void write() {
		// 加密操作
		FileStream::write();
	}
	virtual void seek() {
		// 解密操作
		FileStream::seek();
	}
}

class BufferFileStream: public FileStream {
public:
	...
}

现在仅仅完成FileStream的加密和缓存操作,还有网络流和内存流没写,但它们同CryptoFileStream大体类似。这个类似是指,它们一定会调用父类的read()write()seek()等方法。更明确的说,对CryptoFileStream,一定会有FileStream::read();对CryptoNetworkStream,一定有NetworkStream::read(),对CryptoMemoryStream,一定有MemoryStream::read()

事实上,FileStream、NetworkStream、MemoryStream都继承自Stream。也就是说Stream类型的指针或者引用可以对它们统一管理,我们只要在程序运行时,说明stream表示的是FileStream呢,还是NetworkStream,或者MemoryStream就可以了。

因此代码修改可如下:

class CryptoFileStream: public FileStream {
private:
	Stream* stream;
public:
	CryptoFileStream(Stream* stm): stream(stm) {}
	virtual void read() {
		stream->read();
		// 解密操作
	}
	virtual void write() {
		// 加密操作
		stream->write();
	}
	virtual void seek() {
		// 解密操作
		stream->seek();
	}
};

int main()
{
	FileStream* fs = new FileStream();
	CryptoFileStream* cfs = new CryptoFileStream(fs);  // 动态的方式告诉stream指针指向谁
	...
	return 0;
}

此时会发现,三种流的Crypto都有private: Stream* stream;,显然代码重复了。又因为它们有相同的祖先,是不是意味着可以在祖先类Stream中加上这行语句呢?

——别忙下结论!作为Stream的直接子类FileStream,NetworkStream,MemoryStream都不需要stream指针,所以直接在Stream类中添加,看起来不像是明智的行为。继承不够灵活在这里体现。

装饰模式

事实上,我们就是想给Crypto*类添加一个Stream类型的指针而已——这个“添加”,正在接近“装饰”的意思了。根据上面最后改进得到的代码,已经可以让CryptoFileStream、CryptoNetworkStream、CryptoMemoryStream脱离FileStream、NetworkStream和MemoryStream的掌控。因为尽管我们用到了父类的方法,但并非通过继承的方式使用,而是组合,这是指针stream起到的功效。所以Crypto*类没必要分别继承不同的类,它们只要继承自同一个含有stream字段的类,不就是都有stream指针了吗?

现在,为这个类取名叫DecoratorStream——是的,它是这个装饰模式中的关键部分,它叫抽象装饰类

class DecoratorStream: public Stream {
protected:
	Stream* stream;  // 组合Stream
	DecoratorStream(stream* stm): stream(stm) {}
}

此后,Crypto*类得到救赎——CryptoFileStream、CryptoNetworkStream、CryptoMemoryStream可以合并为一个类:CryptoStream。(当然,这是在三种流加密解密算法一样的情况下,如果不一样,我认为合并为一个类并非最好方式)

class CryptoStream: public DecoratorStream {
public:
	CryptoStream(stream* stm): DecoratorStream(stm) {}
	virtual void read() {
		stream->read();
		// 解密操作
	}
	virtual void write() {
		// 加密操作
		stream->write();
	}
	virtual void seek() {
		// 解密操作
		stream->seek();
	}
}

注意DecoratorStream类,它继承了Stream(继承是为了让它的派生类遵从Stream的接口设计),又组合了Stream(组合是为了动态的使用FileStream等一系列类的功能)。 当看到一个类有这样的行为时,这个类多半就是装饰类了,其设计模式很大概率是装饰模式。

关系梳理

如果一味的继承,那么类与类的关系呈现如下:
在这里插入图片描述

如果使用装饰模式,那么类与类关系如下:
在这里插入图片描述

可以看到,子类的数量也得到了有效控制。

模式结构

装饰模式包含如下角色:

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类
    在这里插入图片描述

总结

装饰模式的优点:

  • 装饰模式可以比继承关系更灵活的扩展扩展对象功能;
  • 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象;
  • 具体构件类(上述中的FileStream、NetworkStream)与具体装饰类(上述中的CryptoStream、BufferStream)能够独立变化。用户可以根据需要增加新的具体构件类和具体装饰类,使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

装饰模式的缺点:

  • 装饰模式灵活的特点,也意味着加大了出错时对其排查问题的困难度。

尽管装饰模式避免了继承关系带来的“多子类衍生问题”,但它的目的并非于此,其旨在:解决“主体类在多个方向上扩展功能”。

感谢

  • 参考李建忠老师的《C++模式设计》
  • 参考《Graphic Design Patterns》中的装饰模式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值