设计模式7-装饰模式

news2024/9/20 18:54:25

设计模式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个功能类。对比于修改之前类的层次。冗余的代码量大大减少同时也减少了子类的数量。

要点总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】11.快速排序

一、快速排序的思想 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右…

深度解密Spark性能优化之道课程

课程通过实战案例解析和性能调优技巧的讲解,帮助学员提升大数据处理系统的性能和效率。课程内容涵盖了Spark性能调优的各个方面,包括内存管理、并行度设置、数据倾斜处理、Shuffle调优、资源配置等关键技术和策略。学员将通过实际案例的演示和分析&#…

【云原生】Kubernetes部署EFK日志分析系统

Kubernetes部署EFK日志分析系统 文章目录 Kubernetes部署EFK日志分析系统一、前置知识点1.1、k8s集群应该采集哪些日志?1.2、k8s比较流行的日志收集解决方案1.3、fluentd、filebeta、logstash对比分析1.3.1、Logstash1.3.2、Filebeat1.3.3、fluentd 1.4、EFK工作原理…

设计模式探索:观察者模式

1. 观察者模式 1.1 什么是观察者模式 观察者模式用于建立一种对象与对象之间的依赖关系,当一个对象发生改变时将自动通知其他对象,其他对象会相应地作出反应。 在观察者模式中有如下角色: Subject(抽象主题/被观察者&#xf…

【数据结构】12.排序

一、排序的概念及其运用 1.1排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记…

(自适应手机端)保健品健康产品网站模板下载

(自适应手机端)保健品健康产品网站模板下载PbootCMS内核开发的网站模板,该模板适用于装修公司网站、装潢公司网站等企业,当然其他行业也可以做,只需要把文字图片换成其他行业的即可;自适应手机端,同一个后台&#xff0…

sql盲注

文章目录 布尔盲注时间盲注 布尔盲注 介绍:在网页只给你两种回显的时候是用,类似于布尔类型的数据,1表示正确,0表示错误。 特点:思路简单,步骤繁琐且麻烦。 核心函数: length()函数substr()函…

ZD屏幕录像机解锁版下载及安装教程 (一款小巧的轻量级屏幕录像工具)

录屏系列软件安装目录 一、超好用的傲软录屏下载和解锁版安装教程 (专业好用的桌面录屏软件)) 二、班迪录屏Bandicam v7解锁版安装教程(高清录屏软件) 三、Mirillis Action v4 解锁版安装教程(专业高清屏幕录像软件) 四、Aiseesoft Scree…

C语言编程3:运算符,运算符的基本用法

C语言3🔥:运算符,运算符的基本用法 一、运算符🌿 🎇1.1 定义 运算符是指进行运算的动作,比如加法运算符"“,减法运算符”-" 算子是指参与运算的值,这个值可能是常数&a…

Apache Spark分布式计算框架架构介绍

目录 一、概述 二、Apache Spark架构组件栈 2.1 概述 2.2 架构图 2.3 架构分层组件说明 2.3.1 支持数据源 2.3.2 调度运行模式 2.3.3 Spark Core核心 2.3.3.1 基础设施 2.3.3.2 存储系统 2.3.3.3 调度系统 2.3.3.4 计算引擎 2.3.4 生态组件 2.3.4.1 Spark SQL 2.…

三菱PLC 实现PID控制温度 手搓PID指令!!!

目录 1.前言 2.PID公式的讲解 3.程序 4.硬件介绍 5.EPLAN图纸 6.成果展示 7.结语 1.前言 新手想要学习PLC的PID控制 首先会被大串的PID 公式吓到 PID公式有很多种:基本PID 位置式 增量式 模拟式 理想型 等等 但是 不要急 别看这么多公式 其实 将公式拆…

如何通过ip地址判断网络类别

在计算机网络中,IP地址不仅是设备在网络中的唯一标识,同时也隐含了网络类别的信息。了解如何根据IP地址判断网络类别,对于网络管理员、系统工程师以及网络爱好者来说都是一项基本技能。本文将详细介绍如何通过IP地址判断网络类别。 一、IP地址…

普中51单片机:矩阵按键扫描与应用详解(五)

文章目录 引言电路图开发板IO连接矩阵键盘的工作原理行列扫描逐行/逐列扫描 LCD1602代码库代码演示——暴力扫描代码演示——数码管(行列式)代码演示——线翻转法代码演示——LCD1602密码锁 引言 矩阵按键是一种通过行列交叉连接的按键阵列,可以有效地减少单片机I/…

LibreOffice的国内镜像安装地址和node.js国内快速下载网站

文章目录 1、LibreOffice1.1、LibreOffice在application-conf.yml中的配置2、node.js 1、LibreOffice 国内镜像包网址:https://mirrors.cloud.tencent.com/libreoffice/libreoffice/ 1.1、LibreOffice在application-conf.yml中的配置 jodconverter:local:enable…

代谢组数据分析一:代谢组数据准备

介绍 该数据集是来自于Zeybel 2022年发布的文章_Multiomics Analysis Reveals the Impact of Microbiota on Host Metabolism in Hepatic Steatosis_ [@zeybel2022multiomics],它包含了多种组学数据,如: 微生物组(粪便和口腔) 宿主人体学指标 宿主临床学指标 宿主血浆代谢…

C语言之数据在内存中的存储(1),整形与大小端字节序

目录 前言 一、整形数据在内存中的存储 二、大小端字节序 三、大小端字节序的判断 四、字符型数据在内存中的存储 总结 前言 本文主要讲述整型包括字符型是如何在内存中存储的,涉及到大小端字节序这一概念,还有如何判断大小端,希望对大…

大语言模型的直接偏好优化(DPO)对齐在PAI-QuickStart实践

直接偏好优化(Direct Preference Optimization,DPO)算法是大语言模型对齐的经典算法之一,它巧妙地将奖励模型(Reward Model)训练和强化学习(RL)两个步骤合并成了一个,使得训练更加快…

Python 基础知识:为什么使用 __init__.py ?

大家好!今天,我们将深入了解 Python 中的 __init__.py 文件,这个小文件却能干大事。让我们抛开任何专业术语,直接进入正题。 什么是 __init__.py ? 假设你有一个 Python 目录,里面有一堆 Python 文件&…

vue3【实战】语义化首页布局

技术要点&#xff0c;详见注释 <script setup></script><template><div class"page"><header>页头</header><nav>导航</nav><!-- 主体内容 --><main class"row"><aside>左侧边栏<s…

JavaDS —— 顺序表ArrayList

顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改。在物理和逻辑上都是连续的。 模拟实现 下面是我们要自己模拟实现的方法&#xff1a; 首先我们要创建一个顺序表&#xff0c;顺序表…