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

news2024/11/24 8:45:14

文章目录

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

一、引言

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

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


二、装饰器模式

装饰器模式也就是在原有行为之上进行拓展,并不会改变该行为。如在进行网络通信的时候,数据是基于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.coloradmin.cn/o/2230641.html

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

相关文章

第7章 内容共享

第 7 章 内容共享 bilibili学习地址 github代码地址 本章介绍Android不同应用之间共享内容的具体方式&#xff0c;主要包括&#xff1a;如何利用内容组件在应用之间共享数据&#xff0c;如何使用内容组件获取系统的通讯信息&#xff0c;如何借助文件提供器在应用之间共享文件…

分布式锁(redisson,看门狗,主从一致性)

目录 分布式锁一&#xff1a;基本原理和实现方式二&#xff1a;分布式锁的实现1&#xff1a;分布式锁的误删问题2&#xff1a;解决误删问题 三&#xff1a;lua脚本解决多条命令原子性问题调用lua脚本 四&#xff1a;Redisson1&#xff1a;redisson入门2&#xff1a;redisson可重…

Java实战项目-基于SpringBoot+Vue的二手车交易系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

JVM学习总结:类的加载篇

本文是学习尚硅谷宋红康老师主讲的尚硅谷JVM精讲与GC调优教程的总结&#xff08;文末有链接&#xff09; 本篇可能被问到的问题&#xff1a; 类的加载过程类加载器 自定义类的加载器、ClassLoader双亲委派机制&#xff0c;破坏此机制的例子 类的加载过程&#xff08;生命周期…

CSS例子: 横向排列的格子

效果 HTML <view class"content"><view class"item" v-for"item of 5">{{item}}</view></view> CSS .content {height: 100vh;display: flex;flex-direction: row; flex-wrap: wrap;align-content: flex-start;backgro…

ElementUI el-form表单多层数组的校验

问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; ElementUI el-form表单多层数组的校验 页面效果&#xff1a; 数据结构&#xff1a; addform: {code: ,type: ,value: ,state: 1,remark: ,fieldList: [{fieldCode: ,resolverEntities: [{resolverType: , re…

房贷利率定价调整机制变更的一点理解

个人理解&#xff1a; 1、已知2024年第三季度全国新发放商业性个人住房贷款加权平均利率为3.33%。 而2024年7月、8月、9月的5年期以上LPR数据分别如下&#xff1a; - 7月20日调整后&#xff0c;5年期以上LPR为3.75%&#xff1b; - 8月的5年期以上LPR与7月相同&#xff0c;…

设计模式讲解01-建造者模式(Builder)

1. 概述 建造者模式也称为&#xff1a;生成器模式 定义&#xff1a;建造者模式是一种创建型设计模式&#xff0c;它允许你将创建复杂对象的步骤与表示方式相分离。 解释&#xff1a;建造者模式就是将复杂对象的创建过程拆分成多个简单对象的创建过程&#xff0c;并将这些简单…

[MySQL]DQL语句(一)

查询语句是数据库操作中最为重要的一系列语法。查询关键字有 select、where、group、having、order by、imit。其中imit是MySQL的方言&#xff0c;只在MySQL适用。 数据库查询又分单表查询和多表查询&#xff0c;这里讲一下单表查询。 基础查询 # 查询指定列 SELECT * FROM …

C/C++语言基础--C++模板与元编程系列三(变量模板、constexpr、萃取等…………)

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 模板与元编程是C的重要特点&#xff0c;也是难点&#xff0c;本人预计将会更新10期左右进行讲解&#xff0c;这是第三期&#xff0c;讲变量模板、constexpr、萃取等知识&#xff1b;C语言后面也会继续更新知…

leetcode155:最小栈

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int get…

探秘机器学习算法:智慧背后的代码逻辑

1、 线性回归 线性回归是预测连续变量的一种简单而有效的方法。其数学模型假设因变量 y 与自变量 x 之间存在线性关系&#xff0c;用公式表示为&#xff1a; ​ Python代码实现 import numpy as np from sklearn.linear_model import LinearRegression import matplotlib.…

Spring中@Import和@ComponentScan注解差异

首先我们定义两个类 进行Component扫描 返回结果 进行Import导入 返回 结果 可以看 我们在对该类的所有bean加载没有任何问题 结果一致 但神奇的地方在于此时 我们把Tiger类头的Component注解去掉 ComponentScan注解无法识别Tiger中的Lion Bean 删掉Component 再进行ComonentS…

Ceph 学习指南 集群部署【 cephadm 】

文章目录 引言初识 Server SANServer SAN 和传统存储对比 Ceph 概述Ceph 的架构设计Ceph 的特点Ceph 块存储Ceph 文件系统Ceph 对象存储Ceph 介绍 Ceph 集群部署配置 aliyun 源配置时间同步配置 hosts 文件安装 docker配置免密登录ceph 集群部署ceph1 配置安装 python3安装 cep…

(JVM)在JVM中,类是如何被加载的呢?本篇文章就带你认识类加载的一套流程!

在讲类加载前&#xff0c;需要先了解一下方法区、堆和直接内存三块内存区域的运行模式 1. 方法区 JVM中的方法去是所有线程中共享的一块区域 它存储了跟类相关的信息 方法区 会在虚拟机被启动时创建。它逻辑上是堆的组成部分 它在不同的jvm厂商中存在的位置可能会不同&…

【Arduino】一分钟快速在vs code 编译开发Arduino

下载Arduino 对于一些开发者来说&#xff0c;Arduino开发较为不方便&#xff0c;不管从代码的阅读性、开发效率等等方面&#xff0c;vs code都要优于Arduino IDE开发&#xff0c;而且vs code开发可以使用插件&#xff0c;比如一些AI代码插件&#xff0c;可以加快开发速率&#…

qt QDialog详解

1、概述 QDialog是Qt框架中用于创建对话框的类&#xff0c;它继承自QWidget。QDialog提供了一个模态或非模态的对话框&#xff0c;用于与用户进行交互。模态对话框会阻塞其他窗口的输入&#xff0c;直到用户关闭该对话框&#xff1b;而非模态对话框则允许用户同时与多个窗口进…

去除windows系统桌面字体的黑影

然后点开设置&#xff0c;关闭以下的2个选项

ssm034学生请假系统+jsp(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;学生请假系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本学生请假系统就是在这…

如何利用8款工具辅助建立需求管理体系

本文中&#xff0c;分享了8款辅助建立需求管理体系的工具&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.Jira&#xff1b;4.Trello&#xff1b;5.ClickUp&#xff1b;6.Notion&#xff1b;7.蓝鲸智云&#xff1b;8.红橘。 在如今快速发展的商业环境中&#xff0c…