设计模式:装饰模式(Decorator)

设计模式:装饰模式(Decorator)

  • 设计模式:装饰模式(Decorator)
    • 模式动机
    • 模式定义
    • 模式结构
    • 时序图
    • 模式实现
    • 在单线程环境下的测试
    • 在多线程环境下的测试
    • 模式分析
    • 优缺点
    • 适用场景
    • 应用场景
    • 应用实例
    • 模式扩展
    • 参考

设计模式:装饰模式(Decorator)

装饰模式(Decorator)属于结构型模式(Structural Pattern)的一种。

结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

结构型模式可以分为类结构型模式和对象结构型模式:

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
  • 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

模式动机

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制:使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
  • 关联机制:即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

模式定义

装饰模式(Decorator)别名为油漆工模式,属于对象结构型模式。

装饰模式允许向一个现有的对象添加新的功能,同时又不改变其结构。就增加对象功能来说,装饰模式比生成子类实现更为灵活。

模式结构

装饰模式(Decorator)包含如下角色:

  • 抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
  • 具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
  • 抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
  • 具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

在这里插入图片描述

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

时序图

在这里插入图片描述

模式实现

抽象组件 Component.h:

#ifndef _COMPONENT_H_
#define _COMPONENT_H_class Component
{
public:virtual void operation() = 0;
};#endif // !_COMPONENT_H_

具体组件 ConcreteComponent.h:

#ifndef _CONCRETE_COMPONENT_H_
#define _CONCRETE_COMPONENT_H_#include <iostream>#include "Component.h"class ConcreteComponent : public Component
{
public:virtual void operation(){std::cout << "ConcreteComponent's normal operation." << std::endl;}
};#endif // !_CONCRETE_COMPONENT_H_

抽象装饰器 Decorator.h:

#ifndef _DECORATOR_H_
#define _DECORATOR_H_#include "Component.h"class Decorator : public Component
{
public:virtual void operation() = 0;
};#endif // !_DECORATOR_H_

具体装饰器:

ConcreteDecoratorA.h:

#ifndef _CONCRETE_DECORATOR_A_H_
#define _CONCRETE_DECORATOR_A_H_#include <iostream>#include "Decorator.h"class ConcreteDecoratorA : public Decorator
{
private:Component* m_pComponent;public:ConcreteDecoratorA(Component* pComponent) : m_pComponent(pComponent) {};virtual ~ConcreteDecoratorA(){delete m_pComponent;}void addBehavior(){std::cout << "Add behavior A." << std::endl;}virtual void operation(){m_pComponent->operation();addBehavior();}
};#endif // !_CONCRETE_DECORATOR_A_H_

ConcreteDecoratorB.h:

#ifndef _CONCRETE_DECORATOR_B_H_
#define _CONCRETE_DECORATOR_B_H_#include <iostream>#include "Decorator.h"class ConcreteDecoratorB : public Decorator
{
private:Component* m_pComponent;public:ConcreteDecoratorB(Component* pComponent) : m_pComponent(pComponent) {};virtual ~ConcreteDecoratorB(){delete m_pComponent;}void addBehavior(){std::cout << "Add behavior B." << std::endl;}virtual void operation(){m_pComponent->operation();addBehavior();}
};#endif // !_CONCRETE_DECORATOR_A_H_

在单线程环境下的测试

测试代码:

#include <stdlib.h>#include "ConcreteComponent.h"
#include "ConcreteDecoratorA.h"
#include "ConcreteDecoratorB.h"int main()
{Component* component = new ConcreteComponent();Decorator* decorator1 = new ConcreteDecoratorA(component);Decorator* decorator2 = new ConcreteDecoratorB(decorator1);decorator2->operation();delete component;delete decorator1;delete decorator2;system("pause");return 0;
}

运行结果:

在这里插入图片描述

在多线程环境下的测试

略。

模式分析

  • 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护。当然,关联关系的缺点是比继承关系要创建更多的对象。
  • 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

优缺点

优点:

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  2. 装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

适用场景

在以下情况下可以使用装饰模式(Decorator):

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  3. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

不能采用继承的情况主要有两类:
第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
第二类是因为类定义不能继承(如final类)。

应用场景

  1. IO流的处理:这是一个典型的装饰者模式的应用。InputSteam和OutputStream是最基本的抽象组件(Component),而各种FilterInputSteam和FilterOutputStream就是具体的装饰器,它们可以实现各种不同的IO流处理功能,如缓冲、压缩、加密等等。
  2. 数据库连接池:连接池为抽象组件(Component),各种不同的连接池实现则是具体的装饰器(Concrete Decorator),它们可以实现不同的连接池缓存策略、连接池大小、超时时间等属性。

应用实例

下面以英雄联盟中李青(盲僧)学技能为例。其中Hero代表英雄(抽象构件)、BlindMonk(盲僧-具体构件)、SkillDecorator(技能-抽象装饰器)、QDecorator、WDecorator、EDecorator、RDecorator为英雄的四个技能(具体装饰器)。

UML 类图:

在这里插入图片描述

完整代码:

#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;class Hero
{
public:virtual void learnSkill() = 0;
};class BlindMonk : public Hero
{
private:string heroName;public:BlindMonk(string name) : heroName(name){};virtual void learnSkill(){cout << "英雄名称:" << heroName << endl;}
};class SkillDecorator : public Hero
{
public:virtual void learnSkill() = 0;
};class QDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:QDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~QDecorator(){delete hero;}void learnQ(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnQ();}
};class WDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:WDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~WDecorator(){delete hero;}void learnW(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnW();}
};class EDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:EDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~EDecorator(){delete hero;}void learnE(){cout << "习得技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnE();}
};class RDecorator : public SkillDecorator
{
private:Hero *hero;string skillName;public:RDecorator(Hero *pHero, string name) : hero(pHero), skillName(name){};~RDecorator(){delete hero;}void learnR(){cout << "习得终极技能:" << skillName << endl;}virtual void learnSkill(){hero->learnSkill();learnR();}
};int main()
{Hero *hero = new BlindMonk("李青");SkillDecorator *q = new QDecorator(hero, "Q:天音波/回音击");SkillDecorator *w = new WDecorator(q, "W:金钟罩/铁布衫");SkillDecorator *e = new EDecorator(w, "E:天雷破/摧筋断骨");SkillDecorator *r = new RDecorator(e, "R:猛龙摆尾");r->learnSkill();system("pause");return 0;
}

运行结果:

在这里插入图片描述

模式扩展

装饰模式的简化:如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。

一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。

参考

  1. https://design-patterns.readthedocs.io/zh-cn/latest/structural_patterns/decorator.html
  2. https://www.runoob.com/design-pattern/decorator-pattern.html
  3. https://blog.csdn.net/weixin_45433817/article/details/131037102
  4. https://blog.csdn.net/weixin_45433817/article/details/131074125

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

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

相关文章

Git多人协作场景的使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Firefox浏览器网页上的按钮点击无效解决办法

我在github下点下载经常不好使&#xff0c;查了原因&#xff0c;原来是浏览器的问题。在Firefox浏览器的设置里面&#xff0c;去掉一些cookies的禁用即可。之后&#xff0c;就可以点击按钮成功响应了。

2024 全新 Javascript 面试题目基础篇

1. JavaScript 是单线程的吗&#xff1f; 没错&#xff0c;JavaScript 是 一种 单线程语言。这意味着它只有 一个调用栈和一个内存堆。每次只执行一组指令。 此外&#xff0c;JavaScript 是同步和阻塞 的性质。这意味着代码是逐行执行的&#xff0c;一个任务必须在下一个任务…

JVM的相关知识

目录 JVM内存划分 类加载过程 类加载中的“双亲委派模型” JVM内存划分 JVM也就是java进程。这个进程一旦跑起来之后&#xff0c;就会从操作系统里&#xff0c;申请一大块内存空间。JVM接下来就要进一步的对这个大的空间进行划分。划分成不同区域&#xff0c;从而每个区域都…

如何实时掌握手机号状态的API利器分析

在移动互联网的时代&#xff0c;手机号码不仅是通信的连接点&#xff0c;也是用户身份的关键识别。手机状态查询API 通过提供实时的手机号码状态查询服务&#xff0c;协助企业和组织更有效地管理用户信息&#xff0c;提升服务流程。 手机状态查询API 通过与电信运营商的数据库进…

Golang | Leetcode Golang题解之第117题填充每个节点的下一个右侧节点指针II

题目&#xff1a; 题解&#xff1a; func connect(root *Node) *Node {start : rootfor start ! nil {var nextStart, last *Nodehandle : func(cur *Node) {if cur nil {return}if nextStart nil {nextStart cur}if last ! nil {last.Next cur}last cur}for p : start; …

C++ CSV 中文 乱码 UTF-8 ANSI

要正确读取包含中文字符的CSV文件&#xff0c;需要确保文件是以UTF-8编码保存的&#xff0c;并且在读取文件时正确处理编码。使用 std::wifstream 并设置其字节流的编码方式可以解决乱码问题。以下是修改后的代码&#xff1a; 代码说明&#xff1a; 设置UTF-8编码&#xff1a;…

学至乎没而后止也

开场白 学至后没而后止也这个题目的原话来自与荀子《劝学》。大家知道什么意思吗&#xff1f;学习要学到你人没了&#xff0c;才算停止了。通俗点说就是只要没学死就往死里学&#xff0c;高雅点说就是要保持终身学习。 在以前说终身学习好像是一种良好习惯或品德&#xff0c;…

43-2 Linux入侵排查实验

环境准备: 老规则,我没有靶场就自己搭建了类似, 这里准备一台CentOS 7虚拟机作为受害者,然后使用CS制作木马并在受害者主机上线,具体过程可以看我之前写的一篇文章: 黑客必备利器:如何在系统上安装和使用 CobaltStrike(简称:CS)_cobalt strike-CSDN博客 最终的效果…

Go语言数据库框架 — Gorm

Go入门之Gorm 框架_go gorm-CSDN博客 https://zhuanlan.zhihu.com/p/677057361 一、简介 Gorm框使用ORM技术&#xff0c;将对象(O)和关系数据库(R)之间的映射(M)抽象出来&#xff0c;开发者通过操作对象的方式操作数据库&#xff0c;不需要直接处理SQL语句&#xff0c;降低了…

CANDela studio之CDDT与CDD

CDDT有更高的权限&#xff0c;作为模板规范CDD文件。 CDD可修改的内容比CDDT少。 CDDT根据诊断协议提供诊断格式&#xff0c;主要就是分类服务和定义服务&#xff0c;一般是OEM释放&#xff0c;然后由供应商细化成自己零部件的CDD文件。 在这里举个例子&#xff0c;OEM在CDDT…

Java | Leetcode Java题解之第117题填充每个节点的下一个右侧节点指针II

题目&#xff1a; 题解&#xff1a; class Solution {Node last null, nextStart null;public Node connect(Node root) {if (root null) {return null;}Node start root;while (start ! null) {last null;nextStart null;for (Node p start; p ! null; p p.next) {if…

编译链接问题

问题描述 C语言在编译的时候&#xff0c;提示链接的时候没有找到相应的方法 问题分析 代码文件结构&#xff1a; test.c test/1.c test/1.h test.c代码&#xff1a; #include “test/1.h” void main() { hello(); } test/1.c代码&#xff1a; void hello() { printf(“hel…

AIGC中国开发者大会:AI Agent中国落地发展现状及多模态结合具身智能的发展展望

引言 2024年5月25日&#xff0c;第三届AIGC中国开发者大会在昆仑巢成功举办。本次大会围绕“AI Agent的国内应用现状及多模态结合具身智能的发展展望”这一主题&#xff0c;邀请了多位知名企业家、投资人以及技术专家&#xff0c;共同探讨大模型在中国各行各业的应用现状及未来…

AURIX TC3xx单片机介绍-启动过程介绍3

如下的内容是英文为主,对于TC3xx芯片启动原理不清楚的,可以给我留言,我来解答你们的问题! 3.2.1 Reset类型识别 Reset类型的识别是用来判断上次的复位是Application Reset还是System Reset还是CPU0 Reset。基于复位的原因,启动软件会运行不同的分支逻辑。复位原因可以通…

文章结尾,铺垫下一章带来的期待

你是否容易在阅读时打瞌睡? 是否有很多买回来的书,放在书架上一年甚至几年都未读完,积满了灰尘? 但是,对于小说和电视剧,你却完全停不下来。每集片尾的预告激发了你持续观看下一集的渴望,带来了无限的期待…… 当你撰写文章或编写工具书时,内容可能呈现出乏味的面貌…

Web前端发射爱心:揭秘技术与创意的交融之旅

Web前端发射爱心&#xff1a;揭秘技术与创意的交融之旅 在数字化的世界中&#xff0c;Web前端技术如同一位巧手的魔术师&#xff0c;将代码转化为绚丽多彩的界面&#xff0c;让用户感受到前所未有的体验。而今天&#xff0c;我们将踏上一段特别的旅程&#xff0c;探索如何使用…

pelican,一个超强的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - pelican。 Github地址&#xff1a;https://github.com/getpelican/pelican 在当今数字化时代&#xff0c;博客已经成为个人和企业分享信息、展示成果的重要途径。静态网站…

React:构建Web应用的未来

引言 在不断发展的Web开发领域&#xff0c;React已经成为一股主导力量&#xff0c;重塑了我们构建用户界面和交互式应用的方式。React由Facebook&#xff08;现Meta&#xff09;开发&#xff0c;由于其创新的基于组件的架构、高效的虚拟DOM渲染和声明式编程风格而广受欢迎。在…

熵值法(熵权法)

熵值法&#xff08;Entropy Method&#xff09;是一种多属性决策分析方法&#xff0c;主要用于权重确定、排序和评价。它在风险评估、资源配置、环境管理等领域得到广泛应用。熵值法的核心思想是基于信息熵的概念&#xff0c;利用信息熵来度量各属性对决策的贡献程度&#xff0…