首页 > 编程语言 >设计模式C++004__装饰器模式

设计模式C++004__装饰器模式

时间:2024-07-25 19:18:29浏览次数:9  
标签:__ ... 加密 Stream void virtual C++ 设计模式 public

设计模式C++004__装饰器模式

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

单一职责模式分类中的设计模式:

装饰器模式,
桥模式

1、装饰器模式:

动机:在某些情况下,我们可能会“过渡地使用继承来扩展对象的功能”,由于继承为类型引入静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各个子类的组合(扩展功能的组合)会导致更多子类的膨胀。

?如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降低到最低?

装饰器模式的定义:
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。--GoF

2、示例:

对文件流,内存流,网络流的,读文件,定位文件,写文件操作。
我们定义一个Stream流,抽象类。然后可以通过继承,获得子类文件流,网络流,内存流。
每种流都具有读流,定位流,写流的功能。实际开发当中每个子类中有1到3种方法。这样,排列组合下来,子类中的代码会急剧膨胀。
如果,我们再有需求变化,例如,我们需要对流进行写前加密,读后加密等等操作。我们可以继承各个2级子类,在这些3级子类中,每个读,写方法都要更改。
这种工作量是难以想象的,而且多处修改难免出错。
将来我们还有可能对这些流进行缓冲,怎么办,我们继续修改源代码?

使用继承来设计:

//业务操作
class Stream{
public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    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();

}

类的关系图:

观察思考,发现,代码大量重复,每个子类基本逻辑和流程很像。

怎么办?答案是,不使用继承,使用组合。
以加密文件子类(CryptoFileStream )为例:
把子类定义代码中的:public FileStream,移动到子类的内部,使它成为一个子类中声明的一个实例变量 FileStream *fileStream;

使用继承来扩展加密文件流这一功能的子类

//扩展操作
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 CryptoFileStream {
FileStream *stream; 
public:
    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 CryptoNetworkStream {
NetworkStream *stream;
public:
    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 CryptoMemoryStream {
MemoryStream *stream;
public:
    virtual char Read(int number){
        
        //额外的加密操作...
        stream->Read(number);//读内存流
    }
    virtual void Seek(int position){
        //额外的加密操作...
         stream->Seek(position);//定位内存流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
         stream->Write(data);//写内存流
        //额外的加密操作...
    }
};

根据重构的原则,当一个类的多个子类被多次声明时,应该直接使用父类来声明来替代。这样就消除了代码重复性。
又由于这些文件中,有这些个虚函数是遵循Stream,规范的。所以 virtual char Read();这些个函数需要继承Stream;
这样,上面三个类直接可以这样声明:

//扩展操作
class CryptoFileStream : public Stream {
  Stream *stream; //这里具体是什么,运行时决定例如 new FileStream();
  //...
};

class CryptoNetworkStream  : public Stream {
  Stream *stream;//这里具体是什么,运行时决定例如 new NetworkStream();
};

class CryptoMemoryStream  : public Stream {
  Stream *stream;//这里具体是什么,运行时决定例如 new MemoryStream();
 //...
};

我们发现上面三个类,又是重复的结构。 Stream *stream;在三个子类中被声明了3次。
我们可以直接把对各种流加密的这3个子类合为一个,也甭管是加密文件流,还是网络流,还是内存流了。类名取CryptoStream ,就代表,只对流加密。

class CryptoStream : public Stream {
  Stream *stream; //这里具体是什么,运行时决定例如 new FileStream();
  public:
    virtual char Read(int number){
        //额外的加密操作...
       stream->Read(number);//读文件流,这里不是使用类作用域符号,而是使用运行时多态,即,指针来调用方法。
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream->Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
         stream->Write(data);//写文件流
        //额外的加密操作...
    }
};

这也会有一个奇特的现象:通过组合替代继承会发现,CryptoStream 即继承了Stream类,里面同时又声明了一个Stream 类型的变量 stream;这是装饰器模式的典型特点。

class CryptoStream : public Stream {
  Stream *stream; 

此时把不需要的代码删除之后代码如下,版本2:

//业务操作
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    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);
   
}

这时已经基本符合装饰器模式的思想了。

不过还可以再优化一下,就是通过继承Stream,延伸出来一个DecoratorStream类,这个类里面只有一个 protected Stream *stream;的声明。
三个对不同流操作的子类,定义为继承自DecoratorStream。

这样实际更规范一下,把三个子类中一样的声明提出来,单独作为一层。更符合重构的原则。
最终得到代码 v3:

//业务操作
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;
    
    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);

}

最终的类的继承关系如下

FileStream,NetworkStream,MemoryStream是三个原有的功能。我们要扩展出加密流和缓冲流的功能。这时候是在原有的类上添加新的功能。且原有的功能基本一致。
DecoratorStream继承自Stream, DecoratorStream中又有 一个Stream的变量声明。新的功能子类继承自这个奇怪有特别的类,运行时这个内部声明的指针就会指向具体的子类实例。

每次要新加功能,就只需要如法炮制。按照规范创建子类。

3、结构:

4、要点总结:

  • 通过使用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”、
  • Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上有表现为has-a Component的组合关系,即Decorator类有使用了另外一个Component类。
  • Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用要点在于解决“主题类在多个方向上的扩展功能”--是为装饰的含义。

======自己当时的理解。
这样其他3个流也可以这样做: 。这里的关键点是,类中声明的这个父类指针,在编译时这个指针代表的东西都是一样的,甚至可以确定就是一个4字节的指针符号。运行时就不一样了,不同的子类会有各自不同的构造方法,也会指向对应的子类实例对象。
这样我们就打破了编译时依赖。利用C++多态特性,把这些依赖关系(组合到类中的原来的父类的实例变量)放到运行时确定。

这里注意到一个新的问题,消除了继承关系,也消除了很多冗余的子类中的代码,这些原来可以通过继承父类,得到的抽象接口我没有继承到,怎么办?
这也简单将父类的父类抽象出来,用子类继承就可以了。
这也会有一个奇特的现象:通过组合替代继承会发现

整体来看,使用组合+抽象接口消除了一层继承关系。这将极大缓解因为继承导致子类快速膨胀的问题。

标签:__,...,加密,Stream,void,virtual,C++,设计模式,public
From: https://www.cnblogs.com/wjw-blog/p/18312171

相关文章

  • 设计模式C++005__桥模式
    设计模式C++005__桥模式也是组合模式的具体体现。1、动机:由于某些类型的古有的实现逻辑,使得他们具有两个变化的维度,乃至多个维度的变化。?如何应对这种“多维度的变化”,如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度。2、桥模式:将......
  • 河南萌新联赛2024第(二)场:南阳理工学院
    1.D-A*BBBB原题链接:https://ac.nowcoder.com/acm/contest/87255/D根据乘法的原理,且b的每一位都相同,最终答案则是错位相加得出的结果,于是我们将a翻转,从个位开始计算,如果当前位置小于a.size就往前累加,但如果大于或等于b.size就从头开始一个一个的减(这个过程可以通过纸上手写乘法计......
  • 设计模式C++007__抽象工厂方法模式
    设计模式C++007__抽象工厂方法模式抽象工厂方法1、动机:在软件系统重,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。?如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种封装机制,来避免客户程序和这种“多系列具体对象......
  • DataOps 新趋势:联通数科如何利用 DolphinScheduler 实现数据一体化管理
    引言在DataOps(数据运营)的推动下,越来越多的企业开始关注数据研发和运营的一体化建设。DataOps通过自动化和流程优化,帮助企业实现数据的高效流转和管理。当前,ApacheDolphinScheduler作为一款开源的分布式调度系统,凭借其灵活的插件机制和强大的调度能力,已经成为许多企业构建数据......
  • 西安理工大学机器人NEXT-E战队 视觉组简介和24届新生暑假自学指引
    视觉组简介和24届新生暑假自学指引1.视觉组是什么RoboMaster机器人竞赛作为一个竞技机器人赛事,利用弹丸攻击对方机器人或对方场地道具装甲板是取得胜利的关键。为了更好的进行打击,仅依靠操作手的手动瞄准是远远不够的,因此。视觉组利用各类算法,开发出稳定的自动瞄准系统,能够极......
  • editormd解析公式遇到的问题
    场景描述我们公司有需要将md渲染呈现到网页上的需求,内部使用了editor.md来完成该功能。但在使用的过程中碰到了如下问题部分特定的latex公式解析不正确如下latex部分$$NV_{n}=NV_{0}\timesP_{0}\timesP_{1}\timesP_{2}\times\dots\timesP_{n}$$......
  • 测试通用技术2
    一、测试流程二、软件测试过程模型V模型W模型H模型X模型三、软件测试过程理念尽早测试测试人员早期参与软件项目尽早的开展测试实行工作全面测试对软件的所有产品进行全面测试软件开发及测试人员(有时包括用户)全面参与到测试工作中全过程测试测试人员......
  • Transformer —— 李沐老师论文跟读
    论文地址:https://arxiv.org/pdf/1706.03762摘要当时的序列转录模型主要依赖于复杂的循环或者卷积神经网络加encoder+decoder架构组成,而论文提出了一种简单的网络架构transformer,在原有的encoder+decoder基础上增加注意力机制,而不使用循环和卷积。引言在引言中提到RNN的缺点......
  • locust 中HttpUser和TaskSet是什么关系
    在Locust中,HttpUser和TaskSet是用来定义用户行为和任务集合的重要组件。HttpUser:HttpUser是一个类,它代表了一个模拟的用户,可以用来模拟HTTP请求。HttpUser可以指定一些属性,比如最小等待时间和最大等待时间(min_wait和max_wait),这些属性控制了两个连续任务之间的随......
  • 栈,队列,链表
     栈堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈......