设计模式 —— 装饰器模式

设计模式 —— 装饰器模式

  • 什么是装饰器模式
    • 通俗解释
  • Component(组件接口)
  • ConcreteComponent(具体组件)
  • Decorator(装饰器接口)
  • ConcreteDecorator(具体装饰器)
  • 装饰器模式优缺点
      • 优点
      • 缺点

今天我们来看一个新的设计模式——装饰器模式

什么是装饰器模式

装饰器模式(Decorator Pattern)是一种设计模式,属于结构型模式的范畴。它的主要目的是动态地给一个对象添加额外的职责(功能),而不需要修改该对象的结构。装饰器模式通过创建包装对象来包裹原始对象,这些包装对象提供了与原始对象相同的接口,但可以在调用前后添加新的处理或责任。

装饰器模式的组成部分包括:

  1. Component(组件接口):定义了所有对象共有的操作接口,无论是装饰器还是被装饰的对象都要实现这个接口。
  2. ConcreteComponent(具体组件):实现了Component接口的具体对象,也就是被装饰的原始对象。
  3. Decorator(装饰器抽象类):持有Component的引用,定义了与Component接口一致的抽象操作,这样装饰器就可以替代被装饰对象。同时,装饰器抽象类提供了一个构造方法,用于传入被装饰的对象。
  4. ConcreteDecorator(具体装饰器):实现了装饰器抽象类,负责具体的装饰逻辑,可以添加额外的职责或行为。

装饰器模式的优势:

  • 灵活性:可以在运行时动态地添加或移除对象的功能,而无需修改对象的代码。
  • 遵循开放封闭原则:对扩展开放(可以轻松添加新的装饰器),对修改封闭(不需要改变现有的组件代码)。
  • 重用性:装饰器可以复用,而且可以组合使用,通过不同的装饰器组合来创造复杂的对象行为。
  • 替代继承:相比多层继承,装饰器模式提供了更灵活的替代方案来扩展功能,减少了类的复杂度。

使用场景:

  • 需要动态、透明地给对象增加职责时。
  • 当不能采用继承达到扩展目的,或者采用继承会导致类爆炸时(继承层次过深或过多)。
  • 当需要为对象添加职责,但又不想硬编码到对象中时,以保持对象的可复用性。

通俗解释

想象一下你去一家咖啡店买咖啡。基础的咖啡就像是一杯简单的美式,没有任何额外添加。装饰器模式就像是在咖啡店中添加调料的过程。

  • 基础咖啡就像是装饰器模式中的“被装饰的对象”,就是那个最原始、最基本的东西,比如一杯纯咖啡。
  • 装饰器则是那些可以往咖啡里加的东西,比如牛奶、糖、巧克力粉等。每个装饰器都围绕着“咖啡”这个核心,你可以选择添加一个或多个装饰器来定制你的咖啡。

在程序里,如果我们要给一个功能(比如基础的“打印日志”功能)增加新能力,比如添加时间戳、或者改变字体颜色,而不改变原本的“打印日志”代码,就可以用装饰器模式。就像是给咖啡加糖不改变咖啡本质,但让咖啡变甜了一样。

所以,装饰器模式就是一种设计方法,它允许我们在不修改原始对象的基础上,通过添加一层层“装饰”(附加功能)来扩展对象的功能,就像你点咖啡时一层层添加各种调料,却还是那杯咖啡,只是功能(味道)更丰富了。

Component(组件接口)

我们这里以咖啡举例,首先我们要完成一个基础的接口:

class CoffeeBase
{
public:// 声明为纯虚函数,要求任何继承此类的子类必须实现该函数// 获取咖啡的描述信息,如名称、口味等virtual std::string getDescription() const = 0;// 声明为纯虚函数,要求任何继承此类的子类必须实现该函数// 返回咖啡的成本价格virtual double getCost() const = 0;// 虚析构函数的声明,确保通过基类指针或引用来删除派生类对象时,// 能够正确调用到派生类的析构函数,防止资源泄露virtual ~CoffeeBase();
};// 虚析构函数的定义,这里是一个空的实现
// 对于基类CoffeeBase来说,可能不需要在析构函数中执行具体操作,
// 但定义它是必要的,尤其是在有多态使用场景下,确保析构链的完整
CoffeeBase::~CoffeeBase() {}

ConcreteComponent(具体组件)

我们提供一个具体组件,SimpleCoffee:

// 简单coffee类,继承自CoffeeBase基类
class SimpleCoffee : public CoffeeBase
{
public:// 重写从基类CoffeeBase继承的getDescription函数// 此函数返回一个描述简单咖啡的字符串,这里是"This is a simple coffee"std::string getDescription() const override{return "This is a simple coffee";}// 重写从基类CoffeeBase继承的getCost函数// 此函数返回简单咖啡的成本价格,设定为5.0元double getCost() const override{return 5.0;}
};

Decorator(装饰器接口)

我们还要实现一个装饰器接口:

// 装饰器基类,继承自CoffeeBase,用于给咖啡添加额外功能或装饰
class CoffeeDecorator : public CoffeeBase
{
protected:// 持有一个指向CoffeeBase的指针,用于存储被装饰的咖啡对象CoffeeBase* baseDecorator;public:// 构造函数,接收一个CoffeeBase类型的指针作为参数// 这个参数实际上是被装饰的基础咖啡实例CoffeeDecorator(CoffeeBase* coffee): baseDecorator(coffee) // 初始化baseDecorator指向传入的coffee{// 构造函数体为空,初始化已经在成员初始化列表中完成}// 重写getDescription函数,委托给被装饰的CoffeeBase对象来获取描述// 这样可以保证装饰后的咖啡描述首先展示的是原始咖啡的信息std::string getDescription() const override{return baseDecorator->getDescription();}// 重写getCost函数,同样委托给被装饰的CoffeeBase对象来计算成本// 使得装饰过程不影响原始咖啡成本的计算,装饰器类可在此基础上累加额外成本double getCost() const override{return baseDecorator->getCost();}
};

ConcreteDecorator(具体装饰器)

具体装饰器实现具体的功能:

// 实现加牛奶的装饰器,继承自CoffeeDecorator
class MilkAdd : public CoffeeDecorator
{
public:// 构造函数,接收一个CoffeeBase类型的指针,用于装饰的原始咖啡MilkAdd(CoffeeBase* coffee): CoffeeDecorator(coffee) // 通过基类构造函数初始化基类部分,传入被装饰的咖啡实例{// 构造函数体为空,因为初始化已经在成员初始化列表中完成}// 重写getDescription函数,在原有咖啡描述后添加" Milk"// 表示这杯咖啡添加了牛奶std::string getDescription() const override{return baseDecorator->getDescription() + " Milk";}// 重写getCost函数,在原有咖啡成本基础上增加3元// 代表添加牛奶的服务费用double getCost() const override{return baseDecorator->getCost() + 3;}
};// 实现加糖的装饰器,同样继承自CoffeeDecorator
class SugarAdd : public CoffeeDecorator
{
public:// 构造函数,接收一个CoffeeBase类型的指针,即要装饰的咖啡SugarAdd(CoffeeBase* coffee): CoffeeDecorator(coffee) // 通过基类构造函数初始化,传入被装饰的咖啡实例{// 构造函数体为空}// 重写getDescription函数,在原有咖啡描述后追加" Sugar"// 表明这杯咖啡添加了糖std::string getDescription() const override{return baseDecorator->getDescription() + " Sugar";}// 重写getCost函数,在原有咖啡成本上增加2元// 代表加糖的额外费用double getCost() const override{return baseDecorator->getCost() + 2;}
};

我们可以一一对应:

这段代码展示了装饰器模式的各个组成部分,具体如下:

  1. Component(组件接口):在这个例子中,CoffeeBase类扮演了组件接口的角色。它定义了一个咖啡对象的基本操作接口,包括getDescription()用于获取咖啡的描述和getCost()用于获取成本价格,还有一个虚析构函数确保通过基类指针可以安全地删除派生类对象。
  2. ConcreteComponent(具体组件)SimpleCoffee类是CoffeeBase的子类,代表了具体组件。它实现了getDescription()getCost(),提供了基础咖啡的描述和成本。
  3. Decorator(装饰器接口):虽然在这个实现中没有显式定义一个独立的装饰器接口类,但CoffeeDecorator类实际上承担了装饰器接口的角色。它继承自CoffeeBase,并持有一个CoffeeBase* baseDecorator指针,通过这个指针调用基础咖啡的操作,同时定义了与CoffeeBase相同的接口方法,允许装饰器可以像咖啡一样被操作。
  4. ConcreteDecorator(具体装饰器)MilkAddSugarAdd类是具体的装饰器类,它们继承自CoffeeDecorator。每个装饰器类都在其getDescription()getCost()方法中添加了额外的功能(例如价格和描述中的" Milk"或" Sugar"),同时调用基类的相应方法以保持原有的功能,体现了装饰器模式的“叠加”特性。

这是整体的代码:

// 使用#pragma once指令防止头文件被多次包含
#pragma once// 引入iostream和string头文件,以便进行输入输出和字符串操作
#include <iostream>
#include <string>// 定义咖啡基础接口类,所有的咖啡类型都需要实现这个接口
class CoffeeBase
{
public:// 纯虚函数,获取咖啡的描述信息,子类必须实现virtual std::string getDescription() const = 0;// 纯虚函数,获取咖啡的成本价格,子类必须实现virtual double getCost() const = 0;// 虚析构函数,确保通过基类指针可以正确删除派生类对象virtual ~CoffeeBase();
};// 基类CoffeeBase的析构函数定义
CoffeeBase::~CoffeeBase() {}// 简单咖啡类,实现CoffeeBase接口,代表不加任何调料的原始咖啡
class SimpleCoffee : public CoffeeBase
{
public:// 重写getDescription函数,返回简单咖啡的描述信息std::string getDescription() const override{return "This is a simple coffee";}// 重写getCost函数,返回简单咖啡的成本价格double getCost() const override{return 5.0;}
};// 装饰器基类,继承自CoffeeBase,用于给咖啡添加额外属性或装饰
class CoffeeDecorator : public CoffeeBase
{
protected:// 持有一个指向CoffeeBase的指针,用于存储被装饰的咖啡实例CoffeeBase* baseDecorator;public:// 构造函数,接受一个CoffeeBase指针,初始化被装饰的咖啡对象CoffeeDecorator(CoffeeBase* coffee):baseDecorator(coffee){}// 重写getDescription函数,调用被装饰咖啡的getDescriptionstd::string getDescription() const override{return baseDecorator->getDescription();}// 重写getCost函数,调用被装饰咖啡的getCostdouble getCost() const override{return baseDecorator->getCost();}
};// 牛奶装饰器类,为咖啡添加牛奶
class MilkAdd : public CoffeeDecorator
{
public:// 构造函数,接受一个CoffeeBase指针,用于装饰的咖啡MilkAdd(CoffeeBase* coffee):CoffeeDecorator(coffee){}// 重写getDescription,在原有描述后添加" Milk"std::string getDescription() const override{return baseDecorator->getDescription() + " Milk";}// 重写getCost,在原价基础上增加3元(牛奶费用)double getCost() const override{return baseDecorator->getCost() + 3;}
};// 糖装饰器类,为咖啡添加糖
class SugarAdd : public CoffeeDecorator
{
public:// 构造函数,接受一个CoffeeBase指针,用于装饰的咖啡SugarAdd(CoffeeBase* coffee): CoffeeDecorator(coffee){}// 重写getDescription,在原有描述后添加" Sugar"std::string getDescription() const override{return baseDecorator->getDescription() + " Sugar";}// 重写getCost,在原价基础上增加2元(糖费用)double getCost() const override{return baseDecorator->getCost() + 2;}
};

装饰器模式优缺点

装饰器模式(Decorator Pattern)作为一种常用的设计模式,有其独特的优点和缺点,下面是其主要特点概述:

优点

  1. 灵活性和可扩展性:装饰器模式允许在不修改原有类代码的情况下动态地扩展对象功能,为对象添加新的职责。这使得系统易于扩展,遵循了开闭原则。
  2. 组合模式的替代:相比于通过继承来扩展功能,装饰器模式提供了更灵活的选择,可以避免类层次过深的问题,因为装饰器可以按需叠加,每个装饰器只关注添加单一功能。
  3. 透明性:对于客户端而言,装饰过的对象和未装饰的对象使用方式相同,因为它们都遵循相同的接口。这使得客户端代码可以不关心对象是否被装饰以及如何被装饰,提高了代码的可读性和可维护性。
  4. 重用性:装饰器类可以被多次使用,也可以用来装饰不同的对象,增加了代码的复用性。

缺点

  1. 过度使用导致复杂性:如果过度使用装饰器,可能会导致系统中有很多细小的装饰器类,这会使得类的结构变得复杂,难以理解与维护。
  2. 调试困难:由于装饰器层层嵌套,当出现错误时,调试和定位问题可能变得较为复杂,特别是当装饰器链很长时。
  3. 性能考量:每添加一个装饰器,就增加了一层间接调用,极端情况下可能会影响执行效率,尽管在大多数现代系统中这通常不是主要问题。
  4. 不易理解:对于不熟悉装饰器模式的新开发者来说,理解装饰器如何运作以及如何正确使用它们可能需要时间。

总的来说,装饰器模式在需要动态、灵活地扩展对象功能的场景下非常有用,但需要权衡其带来的潜在复杂性与维护成本。合理使用可以极大提高软件的可维护性和灵活性。

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

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

相关文章

MySQL—多表查询—子查询(介绍)

一、引言 上一篇博客学习完联合查询。 这篇开始&#xff0c;就来到多表查询的最后一种形式语法块——子查询。 &#xff08;1&#xff09;概念 SQL 语句中嵌套 SELECT 语句&#xff0c;那么内部的 select 称为嵌套查询&#xff0c;又称子查询。 表现形式 注意&#xff1a; …

复数的概念

1. 虚数单位&#xff1a;i 引入一个新数 ‘i’&#xff0c;i又叫做虚数单位&#xff0c;并规定&#xff1a; 它的平方等于 -1&#xff0c;即 i -1。实数可以与它进行四则运算&#xff0c;并且原有的加&#xff0c;乘运算律依然成立。 2.定义 复数的定义&#xff1a;形如 a…

Github 2024-06-09 php开源项目日报Top10

根据Github Trendings的统计,今日(2024-06-09统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Livewire: Laravel中构建动态UI组件的全栈框架 创建周期:1818 天开发语言:PHP协议类型:MIT LicenseStar数量:21388 个F…

CTFHUB-SQL注入-字符型注入

目录 查询数据库名 查询数据库中的表名 查询表中数据 总结 此题目和上一题相似&#xff0c;一个是整数型注入&#xff0c;一个是字符型注入。字符型注入就是注入字符串参数&#xff0c;判断回显是否存在注入漏洞。因为上一题使用手工注入查看题目 flag &#xff0c;这里就不…

LeetCode题练习与总结:买卖股票的最佳时机--121

一、题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获…

GIS数据快捷共享发布工具及操作视频

有网友反映还是不会操作GIS数据快捷共享发布工具&#xff08;建立自己的地图网站&#xff09;&#xff0c;要我录个视频。 好&#xff0c;那就录一个: GIS数据快捷共享发布工具及操作视频 虽然默认例子是二维的&#xff0c;但这个服务器可以为二维、三维系统发布时间服务。都是…

NASA数据集——SARAL 近实时增值业务地球物理数据记录海面高度异常

SARAL Near-Real-Time Value-added Operational Geophysical Data Record Sea Surface Height Anomaly SARAL 近实时增值业务地球物理数据记录海面高度异常 简介 2020 年 3 月 18 日至今 ALTIKA_SARAL_L2_OST_XOGDR 这些数据是近实时&#xff08;NRT&#xff09;&#xff…

SpringCloudAlibaba基础二 Nacos注册中心

一 什么是 Nacos 官方&#xff1a;一个更易于构建云原生应用的动态服务发现(Nacos Discovery )、服务配置(Nacos Config)和服务管理平台。 集 注册中心配置中心服务管理 平台。 Nacos 的关键特性包括: 服务发现和服务健康监测动态配置服务动态 DNS 服务服务及其元数据管理 …

达梦8 探寻达梦排序原理:新排序机制(SORT_FLAG=1)

测试版本&#xff1a;--03134283938-20221019-172201-20018 达梦的排序机制由四个dm.ini参数控制&#xff1a; #maximum sort buffer size in Megabytes &#xff0c;有效值范围&#xff08;1~2048&#xff09; SORT_BUF_SIZE 100 #ma…

mysql数据库打开失败的问题

打不开mysql 1. my.ini未配置路径&#xff1a;找到basedir和datadir&#xff0c;改成如下路径。改完记得去掉井号 &#xff01;&#xff01; 复制路径粘贴&#xff0c;记得去掉井号 &#xff01;&#xff01; 启动&#xff01;方式1 发生系统错误5&#xff1a;没有用管理员身…

Android Media Framework(三)OpenMAX API阅读与分析

这篇文章我们将聚焦Control API的功能与用法&#xff0c;为实现OMX Core、Component打下坚实的基础。 1、OMX_Core.h OMX Core在OpenMAX IL架构中的位置位于IL Client与实际的OMX组件之间&#xff0c;OMX Core提供了两组API给IL Client使用&#xff0c;一组API用于管理OMX组件…

数据库 | 关系数据库设计

第七章 1.简述数据库的设计阶段&#xff1f;&#xff08;简要回答数据库设计步骤&#xff1f;&#xff09;&#xff08;&#xff08;数据库设计有哪几个阶段&#xff1f;&#xff09; 需求分析、概念结构设计、逻辑结构设计、物理结构设计、数据库的实施、数据库的运行和维护…

如何用群晖当异地组网服务器?

在当今信息化时代&#xff0c;远程通信成为了企业和个人之间不可或缺的一部分。特别是对于跨地区的通信需求&#xff0c;一个可靠的异地组网服务器是必不可少的。而群晖&#xff08;Synology&#xff09;作为一款功能强大的网络存储设备&#xff0c;可以被用作办公室或家庭的异…

构建LangChain应用程序的示例代码:24、使用OpenAI工具进行文本提取教程(Extraction with OpenAI Tools)

使用OpenAI工具进行文本提取教程 执行提取任务从未如此简单&#xff01;OpenAI的工具调用功能是完美的选择&#xff0c;因为它允许从文本中提取多种不同类型的元素。 使用支持工具的模型 1106版本之后的模型使用工具&#xff0c;并支持“并行函数调用”&#xff0c;这使得任…

ssm621大湾区旅游推荐系统的设计与实现+vue【已测试】

前言&#xff1a;&#x1f469;‍&#x1f4bb; 计算机行业的同仁们&#xff0c;大家好&#xff01;作为专注于Java领域多年的开发者&#xff0c;我非常理解实践案例的重要性。以下是一些我认为有助于提升你们技能的资源&#xff1a; &#x1f469;‍&#x1f4bb; SpringBoot…

数染色体 算法 python源码

效果图如下&#xff1a; 原图&#xff1a; 完整代码&#xff1a; import cv2 import numpy as np from skimage import measure import randomimage cv2.imread(113.jpg, cv2.IMREAD_GRAYSCALE)blurred_img cv2.GaussianBlur(image, (5, 5), 0)_, binary_image cv2.thresho…

后端启动项目端口冲突问题解决

后端启动项目端口冲突 原因&#xff1a; Vindows Hyper-V虚拟化平台占用了端口。 解决方案一&#xff1a; 查看被占用的端口范围&#xff0c;然后选择一个没被占用的端口启动项目。netsh interface ipv4 show excludedportrange protocoltcp 解决方案二&#xff1a; 禁用H…

spring源码解析-(2)Bean的包扫描

包扫描的过程 测试代码&#xff1a; // 扫描指定包下的所有类 BeanDefinitionRegistry registry new SimpleBeanDefinitionRegistry(); // 扫描指定包下的所有类 ClassPathBeanDefinitionScanner scanner new ClassPathBeanDefinitionScanner(registry); scanner.scan(&quo…

MyBatis中 set标签

1、set标签特点&#xff1a; set标签用于更新语句中set标签解析为set关键字set可以去除跟新语句中无用的逗号通常是和if标签一起使用 2、set标签的使用 编写接口方法编写sql语句 注意 当set标签中有条件成立时就会附加set关键字&#xff0c;字段为null时该列不会被更新。se…

域内攻击 ---> AS-REP Roasting

今天&#xff0c;我们就来讲一下另外一种Roasting攻击 AS-REP Roasting 1.AS-REP Roasting原理 其实和kerberoasting一样&#xff0c;这种攻击也是一种暴力破解的攻击&#xff0c;完全取决于攻击者的字典。 但是不同于Kerberoasting&#xff0c;他不需要用户进行注册SPN&…