c++ 设计模式6 (Decorator 装饰模式)

时间:2023-03-09 03:40:49
c++ 设计模式6 (Decorator 装饰模式)

4. “单一职责”类模式

在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。

典型模式代表: Decorator,Bridge

4.1 Decorator 装饰模式

代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现

实现代码1:

类图结构示意(大量使用继承)

c++ 设计模式6 (Decorator 装饰模式)

数据规模: 假设有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)

c++ 设计模式6 (Decorator 装饰模式)

 //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模式比声场子类(继承)更为灵活(消除重复代码&减少子类个数)

类图:

c++ 设计模式6 (Decorator 装饰模式)

要点总结:

1.通过采用组合并非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“

2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。

3.Decorator模式的目的并非解决”多字类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“(显然file,network与加密,缓冲是两种扩展方向) ——是为”装饰“的含义。

参考文献:

李建忠老师 《C++设计模式》网络课程

《设计模式:可复用面向对象软件的基础》