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. Sql group by 分组取时间最新的一条数据

    with MiPriceTopOne as (select classid,max(dataTime) dataTime,max(id) as id from MiPrice group by cla ...

  2. 60分钟内从零起步驾驭Hive实战学习笔记

    本博文的主要内容是: 1. Hive本质解析 2. Hive安装实战 3. 使用Hive操作搜索引擎数据实战 SparkSQL前身是Shark,Shark强烈依赖于Hive.Spark原来没有做SQL ...

  3. OC动态特性

    今天是2.15周日,快要过年了,我以一个实习生的身份在公司工作了快要两个月了吧,什么是北漂,北漂就是感觉生活节奏变了,以前困了可以上课睡觉,累了可以回家休息数周,人际交往乏了,可以躲起来看着窗外的雨或 ...

  4. BootCamp支持软件4/5

    按 Mac 机型列出的 Boot Camp 要求 不同的 Mac 电脑适用不同版本的 Windows.如果您不知道您拥有的 Mac 是什么机型,请从 Apple 菜单中选取“关于本机”. 每个表格条目 ...

  5. MEF(Managed Extensibility Framework)依赖注入学习

    MSDN官方资料,并且微软还提供了SimpleCalculator sample学习样例 http://msdn.microsoft.com/en-us/library/dd460648(v=vs.1 ...

  6. getConnection 区别

    1. 这是一个接口 package javax.sql; DataSource.class /** * <p>Attempts to establish a connection with ...

  7. GetQueuedCompletionStatus的返回值

    完成端口GetQueuedCompletionStatus返回值的问题 先看看GetQueuedCompletionStatus函数的完整声明:BOOL GetQueuedCompletionStat ...

  8. 非阻塞同步机制与CAS操作

    锁的劣势 Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程 持有守护变量的锁,都采用独占的方式来访问这些 ...

  9. 监听UITextFiled输入文字长度的变化

    需求:如果UITextFiled文字的长度大于零,改变一个按钮的颜色 1.一开始想到的当然是UITextFiled的代理方法,但是在UITextFiled的代理方法中并没有监听这个的这一选项!!! 2 ...

  10. JdkDynamicAopProxy源码

     JdkDynamicAopProxy是通过接口实现动态代理类,主要方法是getProxy(ClassLoader classLoader), 代理类生成之后再调用目标方法时就会调用invoke方法. ...