c++ 设计模式6 (Decorator 装饰模式)
4. “单一职责”类模式
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式代表: Decorator,Bridge
4.1 Decorator 装饰模式
代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现
实现代码1:
类图结构示意(大量使用继承)

数据规模: 假设有n种文件,m种功能操作。该实现方法有(1 + n + n * m! / 2) 数量级的子类;
同时考察59行,79行,98行本身是相同的代码(类似还有很多),存在大量的冗余和重复。
开始重构,见方法2.
//Decorator1.cpp
//业务操作
class Stream{
public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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(); }
实现代码2:
针对上述代码,重构步骤如下:
1)考察 CryptoFileStream ,CryptoNetworkStream,CryptoMemoryStream三个类,将其继承FileStream,NetworkStream,NetworkStream改为组合;即
class CryptoFileStream{
FileStream* stream;
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
// ...seek() write() 同理
}
}
class CryptoNetworkStream{
NetworkStream* stream;
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
//... seek() write() 同理
}
}
class CryptoMemoryStream{
MemoryStream* stream;
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
//... seek() write() 同理
}
}
2)考察上述2行, 13行, 24行, 发现其均为Stream子类, 应使用多态性继续重构。
class CryptoFileStream{
Stream* stream; // = new FileStream()
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
// ...seek() write() 同理
}
}
class CryptoNetworkStream{
Stream* stream; // = new NetworkStream();
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
//... seek() write() 同理
}
}
class CryptoMemoryStream{
Stream* stream; // = newMemoryStream()
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
//... seek() write() 同理
}
}
3)发现三个类是相同的,不同的实现(需求的变化)是在运行时实现,编译时复用,改为一个类即可,命名为CryptoStream。
同时为了保证接口规范(read,seek等仍然是虚函数),继承Stream,出现既有组合,又有继承的情况。
class CryptoStream : public Stream{
Stream* stream; // = new ...
public:
virtual char Read(int number){
//额外的加密操作...
stream -> Read(number);//改用字段方式调用Read()
// ...seek() write() 同理
}
}
4)添加相应构造器,得到此轮重构后的结果,代码如下,主要查看使用方式(运行时装配):
//Decorator2.cpp
class Stream{ public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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); }
实现代码3:
上述实现代码2已经极大地缓解了冗余问题,符合面向对象的设计思想,该轮重构是锦上添花。
重构步骤如下:
考察上述代码,多个子类都有同样的字段(Stream* stream;//...)
应考虑“往上提”,方法有两种,第一种是提到基类(显然不合适,FileStream等并不需要Stream字段 )
所以考虑第二种方法,实现一个“中间类”。
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
CryptoStream等继承中间类DecoratorStream:
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
//...
}
重构完成的最终版本:
FileStream,NetworkStream,MemoryStream等可以创建各自的对象;
但实现加密,缓存功能必须在已有FileStream/NetworkStream等对象基础上;
这些操作本质是扩展操作,也就是“装饰”的含义。
此时类图示意:
这时类的数量为(1 + n + 1 + m)

//Decorator3.cpp
class Stream{ public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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); }
Decorator模式使用动机:
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于基础为类型引入的静态特指,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各个子类的组合(扩展功能的组合)会导致各种子类的膨胀。
模式定义:
动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比声场子类(继承)更为灵活(消除重复代码&减少子类个数)
类图:

要点总结:
1.通过采用组合并非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“
2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
3.Decorator模式的目的并非解决”多字类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“(显然file,network与加密,缓冲是两种扩展方向) ——是为”装饰“的含义。
参考文献:
李建忠老师 《C++设计模式》网络课程
《设计模式:可复用面向对象软件的基础》
c++ 设计模式6 (Decorator 装饰模式)的更多相关文章
- C++设计模式-Decorator装饰模式
Decorator装饰模式作用:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活. UML图如下: Component是定义一个对象接口,可以给这些对象动态地添加职责. ...
- 设计模式09: Decorator 装饰模式(结构型模式)
Decorator 装饰模式(结构型模式) 子类复子类,子类何其多加入我们需要为游戏中开发一种坦克,除了不同型号的坦克外,我们还希望在不同场合中为其增加以下一种多种功能:比如红外线夜视功能,比如水路两 ...
- [学习笔记]设计模式之Decorator
写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Decorator(装饰)模式,可以动态地给一个对象添加一些额外的职能.为了更好地理解这个模式,我们将时间线拉回Bridge模式笔记的 ...
- C#设计模式之九装饰模式(Decorator)【结构型】
一.引言 今天我们要讲[结构型]设计模式的第三个模式,该模式是[装饰模式].我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理解吧,大家一定要看清楚,是“装修”,不是“装饰”. ...
- 设计模式系列之装饰模式(Decorator Pattern)
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装.这种模式创建了一个装饰类,用来包装原 ...
- 《JAVA设计模式》之装饰模式(Decorator)
在阎宏博士的<JAVA与模式>一书中开头是这样描述装饰(Decorator)模式的: 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替 ...
- 设计模式系列之装饰模式(Decorator Pattern)——扩展系统功能
说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...
- C#设计模式系列:装饰模式(Decorator)
1. 装饰模式简介 装饰模式动态地给一个对象添加额外的职责.例如一幅画有没有画框都可以挂在墙上,画就是被装饰者.但是通常都是有画框的.在挂在墙上之前,画可以被蒙上玻璃,装到框子里,所以在画上加一层画框 ...
- 设计模式学习之路——Decorator装饰模式(结构模式)
子类复子类,子类何其多 假如我们需要为游戏中开发一种坦克,除了各种不同型号的坦克外,我们还希望在不同场合中为其增加以下一种或多种功能:比如红外线夜视功能,比如水陆两栖功能,比如卫星定位功能等等. 动机 ...
随机推荐
- jquery对象和js对象,以及它们的互相转换
jq对象无法使用DOM对象的方法,互相都不能用 ***jq对象转换成DOM对象的2中方法 1.$('div')[0下标]:jq对象是类似数组对象有长度,所以可以通过下标查找到它的DOM对象 2.$(' ...
- 【转】Java中只有按值传递,没有按引用传递!
原文链接:http://guhanjie.iteye.com/blog/1683637 今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底 ...
- centos安装postfixadmin
postfixadmin的安装,跟普通网站安装没什么区别 配置好虚拟目录,然后在数据库中创建数据库postfix 修改config.inc.php文件,详细搜索谷歌 访问http://www.你的域名 ...
- 技术文集:万能WINDOWS XP封装
这里将系统封装分为3步:做系统.封装.部署 一.做系统 平台不限,但不建议在虚拟机上制作.CPU及主板芯片没有限制,关于intelide和intelppm的不兼容问题,深度白金3in1并没有删除这些注 ...
- 关于网上流传的四个原版Windows XP_SP2全面了解
如何查看你的XP SP2是否原版?打开Windows/System32/找到EULA这个文本文档(即eula.txt):打开在最后一行:有一个EULAID:XPSP2_RM.0_PRO_RTL_CN ...
- yum添加网易和搜狐源
先进入yum源配置目录 cd /etc/yum.repos.d 备份系统自带的yum源 mv CentOS-Base.repo CentOS-Base.repo.save 163的yum源: wget ...
- jquery的clone方法bug的修复
最近发现jquery的clone的bug,textarea和select的jquery的clone方法有问题,textarea和select的值clone的时候会丢掉,在网上发现一个插件,下载地址如下 ...
- F5 负载均衡 相关资源
F5负载均衡之检查命令的说明http://net.zdnet.com.cn/network_security_zone/2010/0505/1730942.shtml F5培训http://wenku ...
- 基本的TCP编程
int socket(int family,int type,int protocol); family: AF_INET ipv4协议 AF_INET6 ipv6协议 AF_LOCAL unix域协 ...
- Vagrant 快速入门
1. Vagrant功能: Vagrant uses Oracle’s VirtualBox to build configurable, lightweight, and portable virt ...