当前位置: 首页 > news >正文

设计模式之装饰器模式

面向对象设计原则

接口隔离原则:面向对象设计之接口隔离原则-CSDN博客

设计模式

工厂模式 : 设计模式之工厂模式-CSDN博客

迭代器模式:设计模式之迭代器模式-CSDN博客

适配器模式:设计模式之适配器模式-CSDN博客

过滤器模式:设计模式之过滤器模式-CSDN博客

单例模式:设计模式之单例模式-CSDN博客

观察者模式:设计模式之观察者模式-CSDN博客

空对象模式:设计模式之空对象模式-CSDN博客

目录

1.概述

2.结构

3.实现

3.1.示例1:简单实现

3.2.示例2:函数装饰器

4.总结


1.概述

        装饰器模式也是我们日常编程用的比较多的一种设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构,实际上就是现有的类的一个包装。这种模式可以视为结构性模式,应为它提供了一种用于组合类的机制,,使得这些类可以在不改变其代码的情况下进行扩展。

        在装饰器模式中,有一个被装饰的组件(通常是抽象类或接口),它定义了需要实现的方法。然后,有一个装饰器类,它实现了同样的接口,并且包装了被装饰的组件。装饰器类可以在被装饰的组件方式调用之前或之后添加额外的功能。

2.结构

装饰器模式的UML类图如下:

主要角色:

1)抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。

2)具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。

3)抽象装饰器(Decorator):这是一个抽象基类或接口,它也实现了组件接口,并包含一个成员变量用来引用具体组件对象。装饰器可以添加额外的功能,通常通过在装饰器中委托给具体组件实现。

4)具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

        装饰器模式通过嵌套包装多个装饰器对象,可以实现多层次的功能增强。每个具体装饰器类都可以选择性地增加新的功能,同时保持对象接口的一致性。

        在C++中,装饰器模式通常涉及到类的继承和组合,以及对虚函数的使用,以确保适当的方法可以在组合中被调用。这使得C++的装饰器模式非常灵活,可用于构建可维护和可扩展的对象结构。

3.实现

3.1.示例1:简单实现

Decorator.h

#include <iostream>
#include <memory>// 抽象组件
class Component {
public:virtual void doWork() = 0;
};// 具体组件
class ConcreteComponent : public Component {
public:void doWork() override {// 执行具体操作std::cout << "ConcreteComponent doWork..." << std::endl;}
};// 抽象装饰器
class Decorator : public Component {
protected:Component* m_pComponent;
public:Decorator(Component* pComponent) : m_pComponent(pComponent) {}void doWork() override {m_pComponent->doWork();}
};// 具体装饰器
class ConcreteDecorator1 : public Decorator {
public:ConcreteDecorator1(Component* pComponent) : Decorator(pComponent) {}void doWork() override {std::cout << "ConcreteDecorator1 doWork before..." << std::endl;__super::doWork();std::cout << "ConcreteDecorator1 doWork after..." << std::endl;}
};// 具体装饰器
class ConcreteDecorator2 : public Decorator {
public:ConcreteDecorator2(Component* pComponent) : Decorator(pComponent) {}void doWork() override {std::cout << "ConcreteDecorator2 doWork before..." << std::endl;__super::doWork();std::cout << "ConcreteDecorator2 doWork after..." << std::endl;}
};

main.cpp

int main()
{std::unique_ptr<Component> pConcreteComponent(new ConcreteComponent());std::unique_ptr<Component> pConcreteDecorator1(new                     ConcreteDecorator1(pConcreteComponent.get()));std::unique_ptr<Component> pConcreteDecorator2(new ConcreteDecorator2(pConcreteDecorator1.get()));pConcreteComponent->doWork();std::cout << std::endl;pConcreteDecorator1->doWork();std::cout << std::endl;pConcreteDecorator2->doWork();
}

输出:

ConcreteComponent doWork...ConcreteDecorator1 doWork before...
ConcreteComponent doWork...
ConcreteDecorator1 doWork after...ConcreteDecorator2 doWork before...
ConcreteDecorator1 doWork before...
ConcreteComponent doWork...
ConcreteDecorator1 doWork after...
ConcreteDecorator2 doWork after...

pConcreteComponent和pConcreteDecorator1还比较简单,pConcreteDecorator2装饰器就比较复杂了,就好像是在pConcreteDecorator1的基础上套了一个壳,从这点也可以看出装饰器模式结构可以非常的灵活。

3.2.示例2:函数装饰器

        虽然装饰器模式通常用于类,但是同样可以应用于函数。例如,假设代码中有一个特定的函数给你带来了麻烦:你希望记录该函数被调用的所有时间,并在Excel中分析统计信息。当然,这可以通过在调用函数前后放置一些代码来实现,即

cout<< "Entering function XYz\n":
// do the work
cout <<"Exiting function XYZ\n";

        这很好,但从关注点分离的角度来看并不好:我们确实希望将日志功能存储在某个地方,以便复用并在必要时强化它。
        有不同的方法可以实现这一点。一种方法是简单地将整个工作单元作为一个函数提供给某些日志组件:

struct Logger{function<void()> func;string name;
public:Logger(const function<void()>& func, const string& name):func{func},name{name}{}void operator()()const {cout<< "Entering "<< name << "\n";func();cout<< "Exiting "<< name << "\n";}
};

使用这种方法,我们可以编写如下代码来使用它:

Logger([](){ cout << "Hello\n";},“HelloFunction")();// Entering HelloFunction
// Hello
// Exiting HelloFunction

始终有一种选择,可以将函数作为模板参数而不是std::function传人。这导致与前面的结果略有不同:

template <typename Func>
struct Logger2{Func func;string name;
public:Logger2(const Func& func,const string& name):func{func},name{name}{}void operator()()const{cout << "Entering "<< name << endl;func();cout << "Exiting "<< name << endl;}
};

此实现的用途完全不变。我们可以创建一个工具函数来实际创建这样的记录器:

template <typename Func> 
auto make_logger2(Func func,const string& name)
{return logger2<Func>{ func, name };
}

然后像下面这样使用它:

auto call = make_logger2([](){cout <<"Hello!" <<endl; },"HelloFunction");
call();// output same as before

你可能会问 "这有什么意义?" 嗯……我们现在有能力在需要装饰某个函数时创建一个装饰器(其中包含装饰的函数)并调用它。
现在,我们遇到了一个新的挑战:如果要记录调用函数add()的日志,该怎么办呢?此处将add()函数定义如下:

double add(double a, double b)
{cout << a << "+"<< b<<"="<<(a + b)<< endl;return a + b;
}

需要 add()函数的返回值吗?如果需要的话,它将从 1ogger中返回(因为1ogger装饰了add()函数)。这并不容易!但也绝不是不可能。我们再次改进一下记录器:

template <typename R, typename... Args>
struct Logger3<R(Args...)>
{Logger3(function<R(Args...)>func, const string& name): func{func},name{name}{}R operator()(Args ...args){    cout << "Entering"<< name << endl;R result = func(args...);cout <<"Exiting "<< name << endl;return result;}function<R(Args ...)> func;string name;
};

在上述代码中,模板参数R表示返回值的类型,Args表示丽数的参数类型。与之前相同的是,装饰器在必要时调用函数;唯一的区别是opeIator() 返回了一个R,因此采用方法不会丢失返回值。
        我们可以构造另一个工具函数 make_:

template <typename R, typename... Args>
auto make_1ogger3(R(*func)(Args...), const string& name)
{return Logger3<R(Args...)>(function<R(Args...)>(func)name);
}

请注意,这里没有使用std::function,而是将第一个参数定义为普通函数指针,我们现在可以使用此函数实例化日志调用并使用它:

auto logged add = make logger3(add, "Add");
auto result=logged add(2,3);

当然,make_logger3可以被依赖注入取代。这种方法的好处是能够:
1)通过提供空对象而不是实际的记录器,动态地打开和关闭日志记录 。
2)禁用记录器正在记录的代码的实际调用(同样,通过替换其他记录器)。
总之,对于开发人员而言,这是一个有用的工具函数。

4.总结

使用装饰器模式的好处:

1)可以在运行时动态的添加或删除功能。

2)可以递归地组合多个装饰器,从而创建复杂的对象。

3)可以使用不同的装饰器组合来创建不同的行为。

4)可以在不修改原有代码的情况下增加新功能。

总体来说,装饰器模式是一种强大且灵活的设计模式,它可以用于创建可扩展、可定制和可组合的对象。

相关文章:

设计模式之装饰器模式

面向对象设计原则 接口隔离原则&#xff1a;面向对象设计之接口隔离原则-CSDN博客 设计模式 工厂模式 &#xff1a; 设计模式之工厂模式-CSDN博客 迭代器模式&#xff1a;设计模式之迭代器模式-CSDN博客 适配器模式&#xff1a;设计模式之适配器模式-CSDN博客 过滤器模式&#…...

【Java万花筒】缓存与存储:Java应用中的数据处理利器

激发性能之源&#xff1a;深度剖析Java开发中的五大数据缓存与存储方案 前言 在现代软件开发中&#xff0c;高效地处理和存储数据是至关重要的任务。本文将介绍一系列在Java应用中广泛使用的数据缓存与存储库&#xff0c;涵盖了Ehcache、Redisson、Apache Cassandra、Hazelca…...

解决nodejs报错内存泄漏问题,项目无法运行

解决方法一 一、使用 increase-memory-limit npm install increase-memory-limit //本项目中使用// 或 npm install -g increase-memory-limit //全局安装二、安装 npm install --save cross-env 配置package.json文件 LINMIT大小 81928g "scripts": {"f…...

计算机网络-物理层基本概念(接口特性 相关概念)

文章目录 总览物理层接口特性星火模型给出的相关概念解释&#xff08;仅供参考&#xff09; 总览 求极限传输速率&#xff1a;奈氏准则&#xff0c;香农定理&#xff08;背景环境不一样&#xff09; 编码&#xff1a;数据变成数字信号 调制&#xff1a;数字信号变成模拟信号 信…...

从规则到神经网络:机器翻译技术的演化之路

文章目录 从规则到神经网络&#xff1a;机器翻译技术的演化之路一、概述1. 机器翻译的历史与发展2. 神经机器翻译的兴起3. 技术对现代社会的影响 二、机器翻译的核心技术1. 规则基础的机器翻译&#xff08;Rule-Based Machine Translation, RBMT&#xff09;2. 统计机器翻译&am…...

python 面经

关于自身特点 1. 介绍下自己&#xff0c;讲一下在公司做的项目 2. 说一下熟悉的框架&#xff0c;大致讲下其特点 python 基础 1.可变与不可变类型区别 2.请解释join函数 3.请解释*args和**kwargs的含义&#xff0c;为什么使用* args&#xff0c;** kwargs&#xff1f; 4.解释…...

Ubuntu (Linux) 下创建软链接(即符号链接,相当于windows下的快捷方式)方法

Ubuntu (Linux) 下创建软链接&#xff08;即符号链接&#xff0c;相当于windows下的快捷方式&#xff09;方法 使用创建软链接的命令 #命令格式如下。注意&#xff1a;请使用绝对路径&#xff0c;否则链接可能失效 ln -s <源文件或目录的绝对路径> <符号链接文件&am…...

LeetCode.2765. 最长交替子数组

题目 2765. 最长交替子数组 分析 为了得到数组 nums 中的最长交替子数组的长度&#xff0c;需要分别计算以每个下标结尾的最长交替子数组的长度。为了方便处理&#xff0c;计算过程中需要考虑长度等于 1 的最长交替子数组&#xff0c;再返回结果时判断最长交替子数组的长度…...

Springboot日志框架logback与log4j2

目录 Springboot日志使用 Logback日志 日志格式 自定义日志格式 日志文件输出 Springboot启用log4j2日志框架 Springboot日志使用 Springboot底层是使用slf4jlogback的方式进行日志记录 Logback日志 trace&#xff1a;级别最低 debug&#xff1a;调试级别的&#xff0c…...

浪花 - 用户信息展示+更新

1. 用户登录获取登录凭证 已登录的用户才能获取个人信息发送 Aixos 请求登录 const user ref();onMounted(async () > {const res await myAxios.get(/user/current);if (res.code 0) {console.log("获取用户信息成功");user.value res.data;} else {consol…...

xxe漏洞之scms靶场漏洞

xxe-scms 代码审核 &#xff08;1&#xff09;全局搜索simplexml_load_string simplexml_load_string--将XML字符串解释为对象 &#xff08;2&#xff09;查看源代码 ID1 $GLOBALS[HTTP_RAW_POST_DATA]就相当于file_get_contents("php://input"); 因此这里就存…...

Unity3d C#实现三维场景中图标根据相机距离动态缩放功能

前言 如题的需求&#xff0c;其实可以通过使用UI替代场景中的图标来实现&#xff0c;不过这样UI的处理稍微麻烦&#xff0c;而且需要在图标上添加粒子特效使用SpriteRender更方便快捷。这里就根据相机离图标的位置来计算图标的缩放大小即可。这样基本保持了图标的大小&#xf…...

Linux网络编程(二-套接字)

目录 一、背景知识 1.1 端口号 1.2 网络字节序 1.3 地址转换函数 二、Socket简介 三、套接字相关的函数 3.1 socket() 3.2 bind() 3.3 connect() 3.4 listen() 3.5 accept() 3.6 read()/recv()/recvfrom() 3.7 send()/sendto() 3.8 close() 四、UPD客服/服务端实…...

【DeepLearning-1】 注意力机制(Attention Mechanism)

1.1注意力机制的基本原理&#xff1a; 计算注意力权重&#xff1a; 注意力权重是通过计算输入数据中各个部分之间的相关性来得到的。这些权重表示在给定上下文下&#xff0c;数据的某个部分相对于其他部分的重要性。 加权求和&#xff1a; 使用这些注意力权重对输入数据进行加权…...

c++:string相关的oj题(415. 字符串相加、125. 验证回文串、541. 反转字符串 II、557. 反转字符串中的单词 III)

文章目录 1. 415. 字符串相加题目详情代码1思路1代码2思路2 2. 125. 验证回文串题目详情代码1&#xff08;按照要求修改后放到新string里&#xff09;思路1代码2(利用双指针/索引)思路2 3. 541. 反转字符串 II题目详情代码1思路1 4. 557. 反转字符串中的单词 III题目详情代码1&…...

HuoCMS|免费开源可商用CMS建站系统HuoCMS 2.0下载(thinkphp内核)

HuoCMS是一套基于ThinkPhp6.0Vue 开发的一套HuoCMS建站系统。 HuoCMS是一套内容管理系统同时也是一套企业官网建设系统&#xff0c;能够帮过用户快速搭建自己的网站。可以满足企业站&#xff0c;外贸站&#xff0c;个人博客等一系列的建站需求。HuoCMS的优势: 可以使用统一后台…...

VsCode + CMake构建项目 C/C++连接Mysql数据库 | 数据库增删改查C++封装 | 信息管理系统通用代码 ---- 课程笔记

这个是B站Up主&#xff1a;程序员程子青的视频 C封装Mysql增删改查操作_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1m24y1a79o/?p6&spm_id_frompageDriver&vd_sourcea934d7fc6f47698a29dac90a922ba5a3安装mysql:mysql 下载和安装和修改MYSQL8.0 数据库存储…...

HackTheBox - Medium - Linux - Ransom

Ransom 外部信息搜集 端口扫描 循例nmap Web枚举 /api/login 它似乎受nosql注入影响&#xff0c;我们能够登录成功 把返回的cookie丢到cookie editor&#xff0c;回到主页 zip是加密的 Foothold 我们可以得知加密类型是ZipCrypto 谷歌能够找到这篇文章&#xff0c;它将告诉我…...

柠檬微趣面试准备

简单介绍一下spring原理 Spring框架是一个开源的Java应用程序框架&#xff0c;它提供了广泛的基础设施支持&#xff0c;帮助开发者构建Java应用程序。Spring的设计原则包括依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;等&#xff0c;以促使代…...

uniapp嵌套webview,无法返回上一级?

uniapp嵌套webview&#xff0c;如何解决回退问题&#xff1f; 文章目录 uniapp嵌套webview&#xff0c;如何解决回退问题&#xff1f;遇到问题解决方式方式一方式二 场景&#xff1a; 进入首页&#xff0c;自动跳转第三方应用 遇到问题 在设备上运行时&#xff0c;无法回退上…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

break 语句和 continue 语句

break语句和continue语句都具有跳转作用&#xff0c;可以让代码不按既有的顺序执行 break break语句用于跳出代码块或循环 1 2 3 4 5 6 for (var i 0; i < 5; i) { if (i 3){ break; } console.log(i); } continue continue语句用于立即终…...

SQLSERVER-DB操作记录

在SQL Server中&#xff0c;将查询结果放入一张新表可以通过几种方法实现。 方法1&#xff1a;使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构&#xff08;包括列名和数据类型&#xff09;将与查询结果匹配。 SELECT * INTO 新…...

Centos 7 服务器部署多网站

一、准备工作 安装 Apache bash sudo yum install httpd -y sudo systemctl start httpd sudo systemctl enable httpd创建网站目录 假设部署 2 个网站&#xff0c;目录结构如下&#xff1a; bash sudo mkdir -p /var/www/site1/html sudo mkdir -p /var/www/site2/html添加测试…...