C++设计模式结构型模式———装饰模式

文章目录

  • 一、引言
  • 二、装饰器模式
  • 三、总结

一、引言

装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

该模式展现出了运行时的一种扩展能力,以及比继承更强大和灵活的设计视角和设计能力,甚至在有些场合下,不使用该模式很难解决问题。


二、装饰器模式

装饰器模式也就是在原有行为之上进行拓展,并不会改变该行为。如在进行网络通信的时候,数据是基于TCP/IP四层模型进行传输的。通过下图可得知从应用层到物理层,数据每向下走一层就会被封装一层,最后将封装好的数据以比特流的方式发送给接收端,而且最重要的是,封装之后,数据只是变复杂了,并没有改变它是数据的本质。

我们继续使用之前的闯关游戏的例子,在游戏中,主角肯定需要许多界面。例如主角的背包,每个格子放一个为物品。下面我们以最简单的界面为例,列表控件。我们一步一步丰富该控件上的内容。先是一个普通的白板,其次加一个边框,然后再加一个垂直滚动条,最后加一个水平滚动条。

为了在游戏中绘制列表控件,传统的绘制方法可能采取以下步骤,来创建基础控件类:

  1. 首先,创建一个名为ListCtrl的基础类,它提供了draw方法来绘制基本的列表控件。创建一个名字叫作 BorderComponent的类,继承自ListCtrl类用于表示增加了边框的列表控件,提供draw方法绘制自身。

  2. 建一个名字叫作VerScBorderListCtrl的类,继承自BorderListCtrl,用于表示增加了边框又增加了垂直滚动条的列表控件,提供draw方法绘制自身。

  3. 创建一个名字叫作HorScVerScBorderListCtrl的类,继承自VerScBorderListCtrl,用于表示增加了边框又增加了垂直滚动条和水平滚动条的列表控件,提供draw方法绘制自身。

考虑下面两个问题:

  • 问题一:如果需要给列表控件增加新内容,如阴影效果或外发光效果,我们不需要创建新的子类。相反,我们可以创建新的组件类(如ShadowComponentGlowEffectComponent),然后将这些组件添加到列表控件中。
  • 问题二:如果我们想要创建一个没有边框但有垂直滚动条的列表控件,或者一个只有水平滚动条的列表控件,我们同样不需要创建更多的子类。我们可以根据需要组装相应的组件,例如,只添加VerticalScrollBarComponentListCtrl中,或者只添加HorizontalScrollBarComponent

上面这两个问题都会导致子类数量的泛滥,灵活性也非常差,所以,采用继承机制创建子类来解决列表控件的绘制显然不是一个好的解决方案,换一种思路,可以采用组装的方式来解决该问题。

  1. 先创建一个ListCtrl类代表普通列表控件(最基本的列表控件),提供draw方法绘制自身。
  2. 如果给这个普通的列表控件增加一个边框(看成是增加一种功能或者是一个装饰),则形成了一个带边框的列表控件。
  3. 同理,如果给这个普通的列表控件增加一个垂直滚动条,则形成了一个带垂直滚动条的列表控件,再给这个带垂直滚动条的列表控件增加一个水平滚动条,又形成了一个既带垂直滚动条又带水平滚动条的列表控件
  4. 总之,通过增加不同的装饰,可以生成不同的列表控件。

上述通过增加装饰来组装新列表控件的方式非常灵活,可以组装出各种各样的列表控件,例如将边框组装到普通列表控件上,就会形成带有边框的列表控件;再将水平滚动条组装到这个带有边框的列表控件上,就会立即形成带有边框和水平滚动条的列表控件。这种通过组装方式将一个类的功能不断增强的思想(动态的增加新功能),就是装饰模式核心的设计思想。

组装特性:而不是通过继承来添加新特性,我们采用组装的方式。这意味着我们可以创建独立的组件类,如用于表示不同的特性。

首先我们创建一个控件类

class Control {
public:virtual void draw() = 0;	//用于把自身绘制到屏幕上virtual ~Control(){} 
};

上面提到的列表控件ListCtrl以及其他控件,例如文本控件TextCtrl等,应该作为抽象控件的子类,也就是作为具体控件,这里以列表控件作为具体控件的代表,创建
ListCtrl类:

// 列表控件类
class ListCtrl : public Control {
public:virtual void draw(){cout << "绘制普通的列表控件!" << endl;//具体可以用Directx或OpenGL来绘制 }
};

接下来我们定义一个装饰器,这是一个抽象的装饰器类(用于作具体装饰器的父类):

// 抽象的装饰器类
class Decorator : public Control {
public:Decorator(shared_ptr<Control> tmpctrl) : m_control(tmpctrl) {} // 构造函数virtual void draw() override {m_control->draw(); // 调用被装饰控件的draw方法}protected:shared_ptr<Control> m_control; // 用智能指针管理需要被装饰的控件
};

抽象装饰器类(Decorator类)的父类依旧是Control类,这可能会造成一些理解上的困扰。试想,一个普通的列表控件经过装饰器装饰后生成一个新的列表控件(例如带边框的列表控件),该列表控件仍然要绘制自己,所以要有draw方法,因此,这个抽象的装饰器类会继承自Control。从另外一个角度来理解,根据public继承的is-a特性,经过装饰器装饰过的列表控件依旧是列表控件(不要把装饰器单纯理解成装饰器,而是理解成经过包装之后的新控件),所以抽象装饰器类继承自Control类也合乎情理。

Decorator类中有一个m_control成员变量,其类型是Control类型的指针,Control同时作为Decorator类的父类,所以从这个角度来讲,Decorator类与Control类又是一种组合关系。

构造函数的形参也是Control类型的指针,代表的当然是被装饰的控件。虚函数draw中的m_control>draw()调用的是哪个类的draw取决于m_control指向的是哪个对象。

下面给出边框、滚动条等具体装饰器:

// 具体的"边框"装饰器类
class BorderDec : public Decorator {
public:BorderDec(shared_ptr<Control> tmpctrl) : Decorator(tmpctrl) {} // 构造函数virtual void draw() override {Decorator::draw(); // 调用父类的draw方法drawBorder(); // 绘制边框}private:void drawBorder() {cout << "绘制边框!" << endl;}
};// 具体的"垂直滚动条"装饰器类
class VerScrollBarDec : public Decorator {
public:VerScrollBarDec(shared_ptr<Control> tmpctrl) : Decorator(tmpctrl) {} // 构造函数virtual void draw() override {Decorator::draw(); // 调用父类的draw方法drawVerScrollBar(); // 绘制垂直滚动条}private:void drawVerScrollBar() {cout << "绘制垂直滚动条!" << endl;}
};// 具体的"水平滚动条"装饰器类
class HorScrollBarDec : public Decorator {
public:HorScrollBarDec(shared_ptr<Control> tmpctrl) : Decorator(tmpctrl) {} // 构造函数virtual void draw() override {Decorator::draw(); // 调用父类的draw方法drawHorScrollBar(); // 绘制水平滚动条}private:void drawHorScrollBar() {cout << "绘制水平滚动条!" << endl;}
};

给出如下案例:

// 创建基础控件实例
shared_ptr<Control> myControl = make_shared<ListCtrl>();
// 使用装饰器组合功能
shared_ptr<Control> borderedControl = make_shared<BorderDec>(myControl);
shared_ptr<Control> verScrollControl = make_shared<VerScrollBarDec>(borderedControl);
shared_ptr<Control> horScrollControl = make_shared<HorScrollBarDec>(verScrollControl);
// 绘制最终控件
horScrollControl->draw();
/*
绘制普通的列表控件!
绘制边框!
绘制垂直滚动条!
绘制水平滚动条!
*/

在这里插入图片描述

ControlDecorator是继承关系也是组合关系。

Decorator类这边,表示Decorator类中包含Control类的对象指针(m_control)作为成员变量。ListCtrl代表着列表控件这个主体类,而其他继承自Decorator的子类都是装饰器类。

引人“装饰”设计模式的定义(实现意图):动态地给一个对象添加一些额外的职责。就增加功能来说,该模式相比生成子类更加灵活。

装饰模式包含4种角色。

  • 抽象构建Control):具体构件ListCtrl和抽象装饰器类Decorator的共同父类,其中定义了必需的接口(draw),用来实现必需的业务。其引人的目的是让调用者以一致的方式处理未被修饰的对象以及经过修饰之后的对象,实现客户端的透明操作。
  • 具体构建ListCtrl):抽象构件Control的子类,定义具体的构件,实现抽象构件中定义的接口,此后,装饰器就可以给该构件增加额外的方法(职责)。
  • 抽象装饰器类Decorator):抽象构件Control的子类,在其中定义了一个与Control接口一致的接口(draw),子类通过对该接口的扩展,达到装饰的目的。
  • 具体装饰器类BorderDecade、VerScrollBarDec、HorScrollBarDec):作为抽象装饰器类的子类。每个具体装饰器类都增加了一些新的方法(例如drawBorderdrawVerScrollBardrawHorScrollBar等)来修饰该构件或者说扩充该构件的能力,之后通过对draw接口的扩展,来达到最终的修饰目的。

装饰器模式结构

在这里插入图片描述
穿衣服也是是使用装饰的一个例子。 觉得冷时, 你可以穿一件毛衣。 如果穿毛衣还觉得冷, 你可以再套上一件夹克。 如果遇到下雨, 你还可以再穿一件雨衣。 所有这些衣物都 “扩展” 了你的基本行为, 但它们并不是你的一部分, 如果你不再需要某件衣物, 可以方便地随时脱掉。


三、总结

对装饰器模式的总结如下:

  1. 灵活性与可扩展性:装饰器模式避免了传统继承方式导致的子类膨胀问题,使得项目设计更加灵活和可扩展。它提供了一种运行时扩展能力,允许将新功能动态地附加到现有对象上。相较于继承,装饰器模式展现了更强的设计能力和视角,可以视为对继承的更优替代方案。新增的装饰器类能够为对象增加新的职责或能力,符合开闭原则。但使用开闭原则时需谨慎,应该集中在最可能改变的地方,避免滥用和复杂性增加。

  2. 相同父类的优势:装饰器对象与被装饰对象共享相同的父类,使得装饰器可以替代被装饰对象,并支持多个装饰器对同一对象进行包装。这种设计方式确保了 draw 接口的正确继承,同时通过组合为对象增添新的能力。

  3. 小对象的问题:装饰器模式的一个主要缺点是可能导致生成大量小对象。在 main 函数中,plistctrl_b_v 依赖于 plistctrl_bplistctrl。这些小对象之间的差异通常不大,成员变量和成员函数相似度高,过多的小对象会占用资源并影响程序性能,管理起来也相对复杂。例如,为了执行 plistctrl_b_v->draw();,需要同时确保 plistctrl_b_vplistctrl_bplistctrl 对象的有效性。如果在执行过程中不慎删除了某个依赖对象,可能导致程序崩溃。因此,程序员在编写代码时必须小心谨慎,以避免难以排查的错误。

适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

责任链模和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。可以使用装饰来扩展组合树中特定对象的行为。大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。装饰可更改对象的外表, 策略模式则让你能够改变其本质。

装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

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

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

相关文章

「iOS」——知乎日报一二周总结

知乎日报仿写 前言效果Manager封装网络请求线程冲突问题下拉刷新添加网络请求的图片通过时间戳和日期格式化获取时间 总结 前言 前两周内容的仿写&#xff0c;主要完成了首页的仿写&#xff0c;进度稍慢。 效果 Manager封装网络请求 知乎日报的仿写需要频繁的申请网络请求&am…

Profinet、Ethernet/IP 工业以太网无线通信解决方案

在工业现场&#xff0c;我们常常会面临这样的困扰&#xff1a;两个PLC之间、PLC 跟远程IO之间或者PLC 跟伺服之间由于种种原因不方便布线&#xff0c;严重影响了通讯效率和生产进程。为了解决这一难题&#xff0c;三格电子设计了一款工业以太网无线网桥&#xff0c;这款无线网桥…

【数据结构】二叉树——前中后序遍历

一、如何遍历二叉树 以图上这个二叉树作为例子&#xff0c;我们若想要访问二叉树中每一个元素 我们一般是采用递归的方式 比如我们要访问完整个二叉树&#xff0c;我们进行递归先访问根的左子树&#xff0c;然后因为递归再次调用&#xff0c;我们会先一直访问二叉树左子树&…

桑基图在医学数据分析中的更复杂应用示例

桑基图&#xff08;Sankey Diagram&#xff09;能够有效地展示复杂的流动关系&#xff0c;特别适合用于医学数据分析中的多种转归和治疗路径的可视化。接下来&#xff0c;我们将构建一个稍微复杂的示例&#xff0c;展示不同疾病患者在治疗过程中的流动&#xff0c;以及他们的治…

WPS 表格奇数行或偶数行填充颜色(Excel也适用)

今天在做项目测试用例&#xff0c;全都是白色底纹&#xff0c;日以继夜的赶项目 大家都头脑不清晰了&#xff0c;看着容易错行&#xff0c;看我一键更新 如本次测试用例&#xff0c;单元格区域的奇数行填充灰色。 点击单元格最左上角选定区域&#xff0c;然后依次点击【开始】…

RT-DETR:替代YOLO的更快实时对象检测模型(附代码)

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

JavaScript。—关于语法基础的理解—

一、程序控制语句 JavaScript 提供了 if 、if else 和 switch 3种条件语句&#xff0c;条件语句也可以嵌套。 &#xff08;一&#xff09;、条件语句 1、单向判断 &#xff1a; if... &#xff08;1&#xff09;概述 < if >元素用于在判断该语句是否满足特定条…

ComfyUI - ComfyUI 工作流中集成 SAM2 + GroundingDINO 处理图像与视频 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/143359538 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 SAM2 与…

Android13预置应用及授权开发

在android13中&#xff0c;要预置一个对讲应用&#xff0c;从预置和授权&#xff0c;梳理了一下&#xff0c;以便后续查询使用。在此记录 一放置应用 我的apk应用放在vendor下面&#xff0c; 路径&#xff1a;projectroot/vendor/fly/package/apps/DMR/flydmr.apk (vendor/fl…

【密码学】全同态加密基于多项式环计算的图解

全同态加密方案提供了一种惊人的能力 —— 能够在不知道数据具体内容的情况下对数据进行计算。这使得你可以在保持潜在敏感源数据私密的同时&#xff0c;得出问题的答案。 这篇文章的整体结构包括多项式环相关的数学介绍&#xff0c;基于多项式环的加密和解密是如何工作的&…

[java][框架]springMVC(1/2)

目标 知道SpringMVC的优点编写SpringMVC入门案例使用PostMan发送请求掌握普通类型参数传递掌握POJO类型参数传递掌握json数据参数传递掌握响应json数据掌握rest风格快速开发 一、SpringMVC简介 1 SpringMVC概述 问题导入 SpringMVC框架有什么优点&#xff1f; 1.1 Spring…

基于STM32健康监控系统/智能手环/老人健康检测系统/心率血氧血压

基于STM32健康监控系统/智能手环/老人健康检测系统/心率血氧血压 持续更新&#xff0c;欢迎关注!!! 基于STM32健康监控系统/智能手环/老人健康检测系统/心率血氧血压 随着人民生活质量的提高和生活节奏的加快&#xff0c;人体健康监测成为全球关注的焦点之一。基于物联网的人体…

百度文心智能体:巧用汉字笔画生成与汉字搜索插件,打造一个学习汉字的教育类智能体

这篇文章&#xff0c;主要介绍如何巧用汉字笔画生成与汉字搜索插件&#xff0c;打造一个学习汉字的教育类智能体。 目录 一、教育类智能体 1.1、智能体演示 1.2、智能体插件 1.3、智能体prompt &#xff08;1&#xff09;角色和目标 &#xff08;2&#xff09;思考路径 …

Efficient Cascaded Multiscale Adaptive Network for Image Restoration 论文阅读笔记

Efficient Cascaded Multiscale Adaptive Network for Image Restoration 论文阅读笔记 这是新国立和新加坡管理大学发表在ECCV2024上的一篇image restoration的文章&#xff0c;提出了一个新的网络结构ECMA&#xff0c;从实验结果上看在超分&#xff0c;去噪&#xff0c;去模糊…

Python | Leetcode Python题解之第525题连续数组

题目&#xff1a; 题解&#xff1a; class Solution:def findMaxLength(self, nums: List[int]) -> int:# 前缀和字典: key为1的数量和0的数量的差值,value为对应坐标hashmap {0:-1}# 当前1的数量和0的数量的差值counter ans 0for i,num in enumerate(nums):# 每多一个1…

微服务架构深入理解 | 技术栈

微服务架构深入理解 | 技术栈 服务网关 服务网关是在微服务架构中扮演重要角色的组件&#xff0c;它是系统对外的入口&#xff0c;负责接收和处理客户端的请求&#xff0c;并将请求路由到相应的微服务。服务网关常常与API管理、负载均衡、安全认证、流量控制等功能结合&#xf…

Java日志脱敏——基于logback MessageConverter实现

背景简介 日志脱敏 是常见的安全需求&#xff0c;最近公司也需要将这一块内容进行推进。看了一圈网上的案例&#xff0c;很少有既轻量又好用的轮子可以让我直接使用。我一直是反对过度设计的&#xff0c;而同样我认为轮子就应该是可以让人拿去直接用的。所以我准备分享两篇博客…

目标追踪DeepSort

一、卡尔曼滤波 你可以在任何对某个动态系统有 “不确定信息” 的地方使用卡尔曼滤波器&#xff0c;并且可以对系统下一步的行为做出 “有根据的猜测”。即使混乱的现实干扰了你所猜测的干净运动&#xff0c;卡尔曼滤波器通常也能很好地确定实际发生了什么。它还可以利用你可能…

数据结构与算法——Java实现 53.力扣938题——二叉搜索树的范围和

生命的意义 在于活出自我 而不是成为别人眼中的你 —— 24.11.3 938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 …

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖、z-index应用及性能分析

微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖、z-index应用及性能分析 目录 微信小程序scroll-view吸顶css样式化表格的表头及iOS上下滑动表头的颜色覆盖、z-index应用及性能分析 1、iOS在scroll-view内部上下滑动吸顶的现象 正常的上下滑动吸顶…