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 装饰模式)的更多相关文章

  1. C++设计模式-Decorator装饰模式

    Decorator装饰模式作用:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活. UML图如下: Component是定义一个对象接口,可以给这些对象动态地添加职责. ...

  2. 设计模式09: Decorator 装饰模式(结构型模式)

    Decorator 装饰模式(结构型模式) 子类复子类,子类何其多加入我们需要为游戏中开发一种坦克,除了不同型号的坦克外,我们还希望在不同场合中为其增加以下一种多种功能:比如红外线夜视功能,比如水路两 ...

  3. [学习笔记]设计模式之Decorator

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Decorator(装饰)模式,可以动态地给一个对象添加一些额外的职能.为了更好地理解这个模式,我们将时间线拉回Bridge模式笔记的 ...

  4. C#设计模式之九装饰模式(Decorator)【结构型】

    一.引言 今天我们要讲[结构型]设计模式的第三个模式,该模式是[装饰模式].我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理解吧,大家一定要看清楚,是“装修”,不是“装饰”. ...

  5. 设计模式系列之装饰模式(Decorator Pattern)

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装.这种模式创建了一个装饰类,用来包装原 ...

  6. 《JAVA设计模式》之装饰模式(Decorator)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述装饰(Decorator)模式的: 装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替 ...

  7. 设计模式系列之装饰模式(Decorator Pattern)——扩展系统功能

    说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...

  8. C#设计模式系列:装饰模式(Decorator)

    1. 装饰模式简介 装饰模式动态地给一个对象添加额外的职责.例如一幅画有没有画框都可以挂在墙上,画就是被装饰者.但是通常都是有画框的.在挂在墙上之前,画可以被蒙上玻璃,装到框子里,所以在画上加一层画框 ...

  9. 设计模式学习之路——Decorator装饰模式(结构模式)

    子类复子类,子类何其多 假如我们需要为游戏中开发一种坦克,除了各种不同型号的坦克外,我们还希望在不同场合中为其增加以下一种或多种功能:比如红外线夜视功能,比如水陆两栖功能,比如卫星定位功能等等. 动机 ...

随机推荐

  1. jquery对象和js对象,以及它们的互相转换

    jq对象无法使用DOM对象的方法,互相都不能用 ***jq对象转换成DOM对象的2中方法 1.$('div')[0下标]:jq对象是类似数组对象有长度,所以可以通过下标查找到它的DOM对象 2.$(' ...

  2. 【转】Java中只有按值传递,没有按引用传递!

    原文链接:http://guhanjie.iteye.com/blog/1683637 今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底 ...

  3. centos安装postfixadmin

    postfixadmin的安装,跟普通网站安装没什么区别 配置好虚拟目录,然后在数据库中创建数据库postfix 修改config.inc.php文件,详细搜索谷歌 访问http://www.你的域名 ...

  4. 技术文集:万能WINDOWS XP封装

    这里将系统封装分为3步:做系统.封装.部署 一.做系统 平台不限,但不建议在虚拟机上制作.CPU及主板芯片没有限制,关于intelide和intelppm的不兼容问题,深度白金3in1并没有删除这些注 ...

  5. 关于网上流传的四个原版Windows XP_SP2全面了解

    如何查看你的XP SP2是否原版?打开Windows/System32/找到EULA这个文本文档(即eula.txt):打开在最后一行:有一个EULAID:XPSP2_RM.0_PRO_RTL_CN ...

  6. yum添加网易和搜狐源

    先进入yum源配置目录 cd /etc/yum.repos.d 备份系统自带的yum源 mv CentOS-Base.repo CentOS-Base.repo.save 163的yum源: wget ...

  7. jquery的clone方法bug的修复

    最近发现jquery的clone的bug,textarea和select的jquery的clone方法有问题,textarea和select的值clone的时候会丢掉,在网上发现一个插件,下载地址如下 ...

  8. F5 负载均衡 相关资源

    F5负载均衡之检查命令的说明http://net.zdnet.com.cn/network_security_zone/2010/0505/1730942.shtml F5培训http://wenku ...

  9. 基本的TCP编程

    int socket(int family,int type,int protocol); family: AF_INET ipv4协议 AF_INET6 ipv6协议 AF_LOCAL unix域协 ...

  10. Vagrant 快速入门

    1. Vagrant功能: Vagrant uses Oracle’s VirtualBox to build configurable, lightweight, and portable virt ...