设计模式7-装饰模式

设计模式7-装饰模式

  • 写在前面
  • 动机
  • 模式定义
  • 结构
  • 代码推导
    • 原始代码
    • 解决
      • 问题分析
    • 选择装饰模式的理由
      • 1. 职责分离(Single Responsibility Principle)
      • 2. 动态扩展功能
      • 3. 避免类爆炸
      • 4. 开闭原则(Open/Closed Principle)
      • 5. 更好的组合复用
      • 例子对比
      • 详细说明
        • 1. 基本流接口 `Stream`
        • 2. 文件流实现 `FileStream`
        • 3. 装饰器基类 `StreamDecorator`
        • 4. 加密装饰器 `CryptoStream`
        • 5. 缓冲装饰器 `BufferedStream`
        • 6. 使用示例 `Process`
      • 总结
  • 要点总结

写在前面

单一职责模式:

  • 在软件组件的设计中,如果责任划分的不清晰,使用记者得到的结果往往是跟随需求的变化,以及子类的增加而急剧膨胀。同时充值的重复代码。这个时候就应该责任划分清楚。使每个类负责自己的责任模块。这才是单一职责模式的关键。

  • 典型模式:装饰模式(decorator model),桥模式(Bridge model)

动机

  • 在某些情况下,我们可能会过多的使用技巧来扩展对象的功能。由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性。随着子类的增多,也就是扩展功能增多。各种子类的组合(扩展功能组合)。

  • 那么如何使对象功能的扩展能够根据需要来动态的实现?而且同时避免扩展功能增多带来的子类膨胀问题。从而使得任何功能扩展变化所导致的影响降为最低。这就是装饰模式的目的。

模式定义

动态组合的给一个对象增加一些额外的职责,就增加工人而言,装饰模式比生成子类更加灵活。也就是消除重复代码以及减少子类个数。

结构

在这里插入图片描述

代码推导

原始代码

//业务操作
class Stream{
publicvirtual 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();

}


这段代码存在几个缺陷。主要包括类的继承结构复杂,重复代码多,扩展性差等问题。

缺陷分析:

在这里插入图片描述
从上述图像可以看出,按照继承模式去写子类。那么子类的数量将会是1个抽象stream类 ,n(此时n=3)个基础子类,n*m个扩展子类,子类与子类间功能还会交叉。

1.类结构复杂。

  • 类继承关系较为复杂,多个类之间存在多重继承(如CryptoFileStream和BufferedFileStream),且同一类的不同变种都要各自继承主类(FileStream)。
  • 这种设计导致继承树较为复杂,难以维护和扩展。

2.重复代码多

  • CryptoFileStream、CryptoNetworkStream、CryptoMemoryStream中的加密操作代码几乎相同。
  • BufferedFileStream、BufferedNetworkStream、BufferedMemoryStream中的缓冲操作代码也基本一致。

3.扩展性差

  • 如果需要增加新的功能,如压缩操作,需要再新增大量类似的类(如CryptoBufferedCompressedFileStream等),导致类的数量急剧增加,扩展性极差。

解决

那么按照继承的逻辑将加密功能以虚函数的形式写入到基类中,有需要重写加密函数不就行了?

将加密功能作为虚函数写入基类的确是一种方式,但是这种方法会有一些缺点,特别是在职责分离和代码扩展性方面。

问题分析

1. 职责不单一:

  • 如果将加密功能直接写入基类,那么基类需要承担多种职责:基本流操作(读、写、定位)和加密操作。这违反了单一职责原则(SRP),即一个类应该只有一个引起其变化的原因。

2. 代码复杂度增加:

  • 基类中加入加密相关的虚函数后,所有子类都需要考虑加密操作,即使某些子类并不需要加密功能。这会增加代码的复杂度。

3. 扩展性差:

  • 如果未来需要添加新的功能(例如压缩、日志记录等),那么基类将会变得越来越臃肿,不同子类需要覆盖不同的虚函数,扩展性差。

选择装饰模式的理由

选择装饰器模式的理由主要有以下几点:

1. 职责分离(Single Responsibility Principle)

装饰器模式允许将不同的功能(如加密、缓冲)分离到不同的类中,从而使每个类只负责一种职责。这符合单一职责原则(Single Responsibility Principle),使代码更易于理解、维护和扩展。

2. 动态扩展功能

装饰器模式可以在运行时动态地组合对象,添加或移除功能,而不需要修改对象本身。这提供了比继承更灵活的功能扩展方式。例如,可以动态地对一个流对象添加加密功能、缓冲功能,甚至多个功能的组合。

3. 避免类爆炸

如果通过继承来实现功能扩展,每种功能组合都需要一个新的子类,类的数量会迅速增加,导致类爆炸问题。装饰器模式通过将功能封装在独立的装饰器类中,避免了大量的子类定义。

4. 开闭原则(Open/Closed Principle)

装饰器模式使类对扩展开放,对修改关闭。可以通过添加新的装饰器类来扩展功能,而无需修改已有的类。这符合开闭原则,提高了代码的可扩展性和灵活性。

5. 更好的组合复用

装饰器模式允许通过不同的装饰器类进行自由组合,复用功能模块。例如,可以同时添加加密和缓冲功能,而无需创建一个专门的“加密缓冲流”类。

例子对比

继承方式的缺点

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 CryptoFileStream : public FileStream {
    // 实现加密功能
};

class BufferedFileStream : public FileStream {
    // 实现缓冲功能
};

class CryptoBufferedFileStream : public FileStream {
    // 实现加密和缓冲功能
};

装饰器模式的优点

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 StreamDecorator : public Stream {
protected:
    Stream* stream;
public:
    StreamDecorator(Stream* strm) : stream(strm) {}
    virtual char Read(int number) {
        return stream->Read(number);
    }
    virtual void Seek(int position) {
        stream->Seek(position);
    }
    virtual void Write(char data) {
        stream->Write(data);
    }
};

// 加密装饰器
class CryptoStream : public StreamDecorator {
public:
    CryptoStream(Stream* strm) : StreamDecorator(strm) {}
    virtual char Read(int number) {
        // 额外的加密操作...
        return StreamDecorator::Read(number);
    }
    virtual void Seek(int position) {
        // 额外的加密操作...
        StreamDecorator::Seek(position);
        // 额外的加密操作...
    }
    virtual void Write(char data) {
        // 额外的加密操作...
        StreamDecorator::Write(data);
        // 额外的加密操作...
    }
};

// 缓冲装饰器
class BufferedStream : public StreamDecorator {
public:
    BufferedStream(Stream* strm) : StreamDecorator(strm) {}
    virtual char Read(int number) {
        // 额外的缓冲操作...
        return StreamDecorator::Read(number);
    }
    virtual void Seek(int position) {
        // 额外的缓冲操作...
        StreamDecorator::Seek(position);
        // 额外的缓冲操作...
    }
    virtual void Write(char data) {
        // 额外的缓冲操作...
        StreamDecorator::Write(data);
        // 额外的缓冲操作...
    }
};

// 使用示例
void Process() {
    Stream* fileStream = new FileStream();
    Stream* cryptoStream = new CryptoStream(fileStream);
    Stream* bufferedCryptoStream = new BufferedStream(cryptoStream);

    bufferedCryptoStream->Read(100);
    bufferedCryptoStream->Write('A');
    bufferedCryptoStream->Seek(10);

    delete bufferedCryptoStream; // 注意:需要确保正确删除链上的所有装饰器
    delete cryptoStream;
    delete fileStream;
}

详细说明

这段代码演示了如何使用装饰器模式来动态地为基本的文件流添加加密和缓冲功能。以下是代码的详细说明:

1. 基本流接口 Stream

Stream 是一个抽象基类,定义了三个纯虚函数:ReadSeekWrite。所有具体的流类都必须实现这些函数。

class Stream {
public:
    virtual char Read(int number) = 0;
    virtual void Seek(int position) = 0;
    virtual void Write(char data) = 0;
    virtual ~Stream() {}
};
2. 文件流实现 FileStream

FileStream 是一个具体的流类,继承自 Stream 并实现了 ReadSeekWrite 方法。具体的实现细节在代码中没有给出,但假设它们涉及对文件的读写操作。

class FileStream : public Stream {
public:
    virtual char Read(int number) {
        // 读文件流
    }
    virtual void Seek(int position) {
        // 定位文件流
    }
    virtual void Write(char data) {
        // 写文件流
    }
};
3. 装饰器基类 StreamDecorator

StreamDecorator 继承自 Stream,并持有一个指向 Stream 对象的指针 streamStreamDecorator 通过调用 stream 指针的相应方法实现 ReadSeekWrite,从而将所有操作委托给被装饰的流对象。

class StreamDecorator : public Stream {
protected:
    Stream* stream;
public:
    StreamDecorator(Stream* strm) : stream(strm) {}
    virtual char Read(int number) {
        return stream->Read(number);
    }
    virtual void Seek(int position) {
        stream->Seek(position);
    }
    virtual void Write(char data) {
        stream->Write(data);
    }
};
4. 加密装饰器 CryptoStream

CryptoStream 继承自 StreamDecorator,并在调用 StreamDecoratorReadSeekWrite 方法之前或之后添加额外的加密操作。

class CryptoStream : public StreamDecorator {
public:
    CryptoStream(Stream* strm) : StreamDecorator(strm) {}
    virtual char Read(int number) {
        // 额外的加密操作...
        return StreamDecorator::Read(number);
    }
    virtual void Seek(int position) {
        // 额外的加密操作...
        StreamDecorator::Seek(position);
        // 额外的加密操作...
    }
    virtual void Write(char data) {
        // 额外的加密操作...
        StreamDecorator::Write(data);
        // 额外的加密操作...
    }
};
5. 缓冲装饰器 BufferedStream

BufferedStream 继承自 StreamDecorator,并在调用 StreamDecoratorReadSeekWrite 方法之前或之后添加额外的缓冲操作。

class BufferedStream : public StreamDecorator {
public:
    BufferedStream(Stream* strm) : StreamDecorator(strm) {}
    virtual char Read(int number) {
        // 额外的缓冲操作...
        return StreamDecorator::Read(number);
    }
    virtual void Seek(int position) {
        // 额外的缓冲操作...
        StreamDecorator::Seek(position);
        // 额外的缓冲操作...
    }
    virtual void Write(char data) {
        // 额外的缓冲操作...
        StreamDecorator::Write(data);
        // 额外的缓冲操作...
    }
};
6. 使用示例 Process

Process 函数中,首先创建一个 FileStream 对象,然后将其装饰为 CryptoStreamBufferedStream,最终形成一个既具有加密功能又具有缓冲功能的流对象。

void Process() {
    Stream* fileStream = new FileStream(); // 基本的文件流
    Stream* cryptoStream = new CryptoStream(fileStream); // 加密装饰
    Stream* bufferedCryptoStream = new BufferedStream(cryptoStream); // 缓冲装饰

    bufferedCryptoStream->Read(100); // 读取操作,包含加密和缓冲
    bufferedCryptoStream->Write('A'); // 写入操作,包含加密和缓冲
    bufferedCryptoStream->Seek(10); // 定位操作,包含加密和缓冲

    delete bufferedCryptoStream; // 注意:需要确保正确删除链上的所有装饰器
    delete cryptoStream;
    delete fileStream;
}

总结

  • 职责分离:每个装饰器类(如 CryptoStreamBufferedStream)只负责增加一个特定的功能,使得代码更符合单一职责原则。
  • 动态组合:通过层层包装,可以在运行时动态地为基本流对象添加多种功能(如加密和缓冲),提高了代码的灵活性和可扩展性。
  • 代码复用:装饰器类可以复用,避免了为每个功能组合创建大量子类的情况。

在这里插入图片描述

从上图可以看出此时类的结构为1+n+1+m。一个抽象类加上n个基础类,加上一个装饰类。再加上m个功能类。对比于修改之前类的层次。冗余的代码量大大减少同时也减少了子类的数量。

要点总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/784835.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何忽略部分文件或者文件夹在git提交项目时

嗨,我是兰若,最近发现有些小伙伴在提交代码时,总是把不该提交的文件,比如说本地批跑的缓存文件给提交到了git上面,导致别人在拉取代码的时候,也会把这部分文件拉取到自己本地,从而导致和本地的缓…

深度学习(笔记内容)

1.国内镜像网站 pip使用清华源镜像源 pip install <库> -i https://pypi.tuna.tsinghua.edu.cn/simple/ pip使用豆瓣的镜像源 pip install <库> -i https://pypi.douban.com/simple/ pip使用中国科技大学的镜像源 pip install <库> -i https://pypi.mirro…

PyCharm如何安装requirements.txt中的依赖包

问题&#xff1a;下载别人的源码&#xff0c;如何安装代码中requirement.txt中的依赖包。 解决方案&#xff1a; &#xff08;1&#xff09;打开PyCharm下面的Terminal&#xff0c;先为代码创建单独的虚拟环境并进入到虚拟环境中&#xff08;每个项目单独的环境&#xff0c;这…

HINet: Half Instance Normalization Network for Image Restoration

论文&#xff1a;HINet: Half Instance Normalization Network for Image Restoration Abstract&#xff1a; 在本文中&#xff0c;我们探讨了实例归一化在低级视觉任务中的作用。 具体来说&#xff0c;我们提出了一个新颖的块&#xff1a;半实例归一化块&#xff08;HIN 块&…

Oracle 19c 统一审计表清理

zabbix 收到SYSAUX表空间告警超过90%告警&#xff0c;最后面给出的清理方法只适合ORACLE 统一审计表的清理&#xff0c;传统审计表的清理SYS.AUD$不适合&#xff0c;请注意。 SQL> Col tablespace_name for a30 Col used_pct for a10 Set line 120 pages 120 select total.…

Linux系统下的用户管理模式

Linux系统下的用户管理模式 本文以属于Linux系统基本概念&#xff0c;如果以查找教程教程&#xff0c;解决问题为主&#xff0c;只需要查看本文后半部分。 如需要系统性学习请查看本文前半部分。 文章目录 Linux系统下的用户管理模式1. Linux下用户的概念2. 创建不同类型的用户…

C++继承(一文说懂)

目录 一&#xff1a; &#x1f525;继承的概念及定义1.1 继承的概念1.2 继承定义1.2.1 定义格式1.2.2 继承关系和访问限定符1.2.3 继承基类成员访问方式的变化 二&#xff1a;&#x1f525;基类和派生类对象赋值转换三&#xff1a;&#x1f525;继承中的作用域四&#xff1a;&a…

SpringSecurity中文文档(Servlet Authorization Architecture )

Authorization 在确定了用户将如何进行身份验证之后&#xff0c;还需要配置应用程序的授权规则。 Spring Security 中的高级授权功能是其受欢迎的最有说服力的原因之一。无论您选择如何进行身份验证(无论是使用 Spring Security 提供的机制和提供者&#xff0c;还是与容器或其…

基于单片机的空调控制器的设计

摘 要 &#xff1a; 以单片机为核心的空调控制器因其体积小 、 成本低 、 功能强 、 简便易行而得到广泛应用 。 本设计通过 &#xff21;&#xff34;&#xff18;&#xff19;&#xff33;&#xff15;&#xff12; 控制&#xff24;&#xff33;&#xff11;&#xff18;&a…

基于YOLOv9的脑肿瘤区域检测

数据集 脑肿瘤区域检测&#xff0c;我们直接采用kaggle公开数据集&#xff0c;Br35H 数据中已对医学图像中脑肿瘤位置进行标注 数据集我已经按照YOLO格式配置好&#xff0c;数据内容如下 数据集中共包含700张图像&#xff0c;其中训练集500张&#xff0c;验证集200张 模型训…

全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法-模拟串口传感器和主机

全网最适合入门的面向对象编程教程&#xff1a;11 类和对象的 Python 实现-子类调用父类方法-模拟串口传感器和主机 摘要&#xff1a; 本节课&#xff0c;我们主要讲解了在 Python 类的继承中子类如何进行初始化、调用父类的属性和方法&#xff0c;同时讲解了模拟串口传感器和…

【问题记录】Nodeclub运行make install报错npm ERR! code ELIFECYCLE

问题展示 按照官网给出的教程进行到make install这一步卡住了&#xff0c;显示了如下报错。 解决方法 将node.js版本变更为能够识别代码的版本&#xff0c;我将版本修改成了16.14.0以后成功运行。 nvm install 16.14.0

Java设计模式---(创建型模式)工厂、单例、建造者、原型

目录 前言一、工厂模式&#xff08;Factory&#xff09;1.1 工厂方法模式&#xff08;Factory Method&#xff09;1.1.1 普通工厂方法模式1.1.2 多个工厂方法模式1.1.3 静态工厂方法模式 1.2 抽象工厂模式&#xff08;Abstract Factory&#xff09; 二、单例模式&#xff08;Si…

从零开始做题:好怪哦

题目 给出一个压缩文件 解题 方法1 01Edit打开&#xff0c;发现是个反着的压缩包&#xff08;末尾倒着的PK头&#xff09; import os# 目标目录路径 # target_directory /home/ai001/alpaca-lora# 切换到目标目录 # os.chdir(target_directory)# 打印当前工作目录以确认…

前端开发过程中经常遇到的问题以及对应解决方法 (持续更新)

我的朋友已经工作了 3 年&#xff0c;他过去一直担任前端工程师。 不幸的是&#xff0c;他被老板批评了&#xff0c;因为他在工作中犯了一个错误&#xff0c;这是一个非常简单但容易忽视的问题&#xff0c;我想也是很多朋友容易忽视的一个问题。 今天我把它分享出来&#xff…

前端面试题27(在实际项目中,如何有效地利用Vue3的响应式系统提高性能?)

在实际项目中&#xff0c;有效利用Vue3的响应式系统提高性能主要涉及以下几个关键点&#xff1a; 1. 合理使用reactive和ref reactive&#xff1a;用于将复杂的数据结构&#xff08;如对象或数组&#xff09;转换成响应式版本。确保只将需要实时更新的数据结构声明为响应式&am…

elasticSearch的索引库文档的增删改查

我们都知道&#xff0c;elasticsearch在进行搜索引擎的工作时&#xff0c;是会先把数据库中的信息存储一份到elasticsearch中&#xff0c;再去分词查询等之后的工作的。 elasticsearch中的文档数据会被序列化为json格式后存储在elasticsearch中。elasticsearch会对存储的数据进…

4.Python4:requests

1.requests爬虫原理 &#xff08;1&#xff09;requests是一个python的第三方库&#xff0c;主要用于发送http请求 2.正则表达式 #正则表达式 import re,requests str1aceace #A(.*?)B,匹配A和B之间的值 print(re.findall(a(.*?)e,str1))import re,requests str2hello com…

Redis-Jedis连接池\RedisTemplate\StringRedisTemplate

Redis-Jedis连接池\RedisTemplate\StringRedisTemplate 1. Jedis连接池1.1 通过工具类1.1.1 连接池&#xff1a;JedisConnectionFactory&#xff1a;1.1.2 test&#xff1a;&#xff08;代码其实只有连接池那里改变了&#xff09; 2. SpringDataRedis&#xff08;lettuce&#…

滑动窗口(同向的双指针)

通过 双指针的 同向移动 算法应用的场景&#xff1a; 满足xxx条件&#xff08;计算结果&#xff0c;出现次数&#xff0c;同时包含&#xff09; 最长/最短 子串 /子数组/子序列 例如&#xff1a;长度最小的子数组 滑动窗口 使用思路 &#xff08;寻找最长&#xff09; –核心…