【C++设计模式】详解装饰模式

news2025/4/17 3:36:01

2023年8月31日,周四上午

这是我目前碰到的最难的设计模式.....

非常难以理解而且比较灵活多半,学得贼难受,写得贼费劲.....

2023年8月31日,周四晚上19:48

终于写完了,花了一天的时间来学习装饰模式和写这篇博客。

虽然基本上把我今天的收获都写下来了,

但感觉写得还是不够好,有很多东西没有表达出来、表达清楚,

以后有空再更新吧....


目录

  • 概述
  • 装饰模式的含义
  • 使用装饰模式的好处
  • 为什么需要装饰模式
  • 什么时候使用装饰模式
  • 情景
  • 标准的装饰模式
  • 装饰模式的主要特征
  • 标准装饰模式程序示例
  • 我对标准装饰模式的思考
  • 使用标准装饰模式来写的示例程序

概述

装饰模式的含义

装饰模式是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。

使用装饰模式的好处

  • 可以动态地给对象添加责任,扩展对象功能。
  • 相比继承,装饰模式使用对象组合而不是继承来扩展功能。更加灵活。
  • 装饰模式支持开闭原则,扩展对象不修改其代码。

为什么需要装饰模式

  1. 动态扩展功能:装饰模式允许在运行时动态地给对象添加新的功能,而不需要修改原始对象的代码。这样可以避免类的继承层次的爆炸性增长,同时也更加灵活和可扩展。
  2. 单一职责原则:装饰模式通过将功能分散到多个装饰类中,每个装饰类只关注特定的功能,遵循了单一职责原则,使得代码更加清晰、可维护。
  3. 开放-封闭原则:装饰模式支持对现有代码的扩展,而不需要修改已有的代码,符合开放-封闭原则。这样可以避免因为修改现有代码而引入潜在的风险和错误。

什么时候使用装饰模式

  1. 需要在不改变现有对象结构的情况下,给对象增加新的功能或责任。
  2. 需要动态地给对象添加功能,并且这些功能可以组合和排列。
  3. 需要扩展一个类的功能,但是使用继承会导致类的继承层次过于庞大或不可行。
  4. 需要在运行时动态地给对象添加功能,而不是在编译时静态地确定功能。

情景

假设我有一个类Xxx,需要给它添加加法功能和减法功能,

但是由于这个类已经很复杂了、而且继承的层次已经很深了,

我不想改动里面的代码,也不想再多弄一个子类,那么怎么添加这两个功能呢?

class Xxx:public Ppp{
public:
	void func1();
	void func2();
	void func3();
	void func4();
	void func5();
	void func6();
	void func7();
};

在这种情况下,可以使用装饰模式来添加这两个功能,

因为这样既不用改动类内部代码,也不用再多弄一个子类。

// 原始类
class Xxx :public Ppp{
public:
  void func1() { /* 原始功能1的实现 */ }
  void func2() { /* 原始功能2的实现 */ }
  // ... 其他原始功能的实现
  void func7() { /* 原始功能7的实现 */ }
};

// 装饰类 - 加法功能
class AddDecorator : public Xxx {
private:
  Xxx* component;

public:
  AddDecorator(Xxx* component) : component(component) {}

  void func1() override {
    component->func1();
    // 加法功能的实现
  }
};

// 装饰类 - 减法功能
class SubtractDecorator : public Xxx {
private:
  Xxx* component;

public:
  SubtractDecorator(Xxx* component) : component(component) {}

  void func2() override {
    component->func2();
    // 减法功能的实现
  }
};

int main() {
  Xxx* xxx = new Xxx();

  // 使用加法功能的装饰类
  Xxx* xxxWithAddition = new AddDecorator(xxx);
  xxxWithAddition->func1();

  // 使用减法功能的装饰类
  Xxx* xxxWithSubtraction = new SubtractDecorator(xxx);
  xxxWithSubtraction->func2();

  delete xxxWithSubtraction;
  delete xxxWithAddition;
  delete xxx;

  return 0;
}

注意,这并不是一个完整的装饰模式,但有助于理解装饰模式的作用。

标准的装饰模式

装饰模式的主要特征

  • 有一个抽象构件类,定义对象的接口。
  • 具体构件类实现抽象构件类。
  • 有一个装饰类继承抽象构件类,包含抽象构件类的对象引用。
  • 装饰类可以调用父类接口实现扩展功能。

标准装饰模式程序示例

#include <iostream>

// 抽象构件类
class Component {
public:
  virtual void func() = 0;
};

// 具体构件类
class ConcreteComponent : public Component {
public:
  void func() override {
    std::cout << "具体构件的操作" << std::endl;
  }
};

// 装饰类
class Decorator : public Component {
protected:
  Component* component;

public:
  Decorator(Component* component) : component(component) {}

  void func() override {
    if (component != nullptr) {
      component->func();
    }
  }
};

// 具体装饰类A
class ConcreteDecoratorA : public Decorator {
public:
  ConcreteDecoratorA(Component* component) : Decorator(component) {}

  void addedBehavior() {
    std::cout << "具体装饰类的附加行为A" << std::endl;
  }

  void func() override {
    Decorator::func();
    addedBehavior();
  }
};

// 具体装饰类B
class ConcreteDecoratorB : public Decorator {
public:
  ConcreteDecoratorB(Component* component) : Decorator(component) {}

  void addedBehavior() {
    std::cout << "具体装饰类的附加行为B" << std::endl;
  }

  void func() override {
    Decorator::func();
    addedBehavior();
  }
};

int main() {
  Component* component = new ConcreteComponent();
  
  //用decoratedComponentA修饰component
  Component* decoratedComponentA = new ConcreteDecoratorA(component);
  //用decoratedComponentB修饰被decoratedComponentA修饰过的component
  Component* decoratedComponentB = new ConcreteDecoratorB(decoratedComponentA);
  
  decoratedComponentB->func();
  
  //要记得释放申请的堆内存
  delete decoratedComponentA;
  delete decoratedComponentB;
  delete component;

  return 0;
}

我对标准装饰模式的思考

装饰模式是怎么实现的不改动类原来的代码和结构就增加新的功能的?
装饰器是如何装饰具体构建类的?

他们有同一个抽象父类
这意味这什么?它们都会有抽象构件类的纯虚函数

装饰器需要引入抽象构件类
能引入抽象构件类,意味传入具体构件类对象后,只能调用其中抽象构件类有的方法
装饰器会重写父类的方法,并在重写的方法里面调用具体构件类的方法

具体装饰器有自己独特的成员和方法
具体装饰器会把具体构件类装入
具体装饰器会重写抽象装饰器的方法,并在重写的方法里面调用父类的方法和自己独特的方法
不管怎样,具体装饰类必须引入抽象构件类并传入具体构件类对象

装饰模式的核心在于重写?
重写的时候加入额外的方法

具体构件类需要实现抽象构件类的纯虚函数,否则无法被创建

装饰器的作用是什么?
引入具体构件类
在重写方法中调用具体构件类的方法

抽象构件类的作用是什么?

具体构件类的作用是什么?
定义最基础的功能

具体装饰器的作用是什么?
写额外的功能,然后通过重写方法加入额外的功能

使用标准装饰模式来写的示例程序

#include<iostream>

class Product{
public:
	virtual void buy()=0;
};

class Computer:public Product{
public:
	void buy ()override{
		std::cout<<"买了一台电脑"<<std::endl;
	}
};

class Decorate:public Product{
private:
	Product *product;
public:
	Decorate(Product *product):product(product){};
	
	void buy() override{
		if(product!=nullptr){
			product->buy();
		}
	}
};

class Mouse:public Decorate{
public:
	Mouse(Product *product):Decorate(product){};
	
	void buy() override{
		Decorate::buy();
		std::cout<<"又买了鼠标"<<std::endl;
	}
};

class KeyBoard:public Decorate{
public:
	KeyBoard(Product *product):Decorate(product){};
	
	void buy() override{
		Decorate::buy();
		std::cout<<"又买了键盘"<<std::endl;
	}
};

int main(){
	Product *computer=new Computer();
	Mouse *mouse=new Mouse(computer);
	KeyBoard *keyBoard=new KeyBoard(mouse);
	keyBoard->buy();
}

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

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

相关文章

《Kubernetes部署篇:Ubuntu20.04基于二进制安装安装kubeadm、kubelet和kubectl》

一、背景 由于客户网络处于专网环境下&#xff0c; 使用kubeadm工具安装K8S集群&#xff0c;由于无法连通互联网&#xff0c;所有无法使用apt工具安装kubeadm、kubelet、kubectl&#xff0c;当然你也可以使用apt-get工具在一台能够连通互联网环境的服务器上下载kubeadm、kubele…

说说Kappa架构

分析&回答 对于实时数仓而言&#xff0c;Lmabda架构有很明显的不足&#xff0c;首先同时维护两套系统&#xff0c;资源占用率高&#xff0c;其次这两套系统的数据处理逻辑相同&#xff0c;代码重复开发。 能否有一种架构&#xff0c;只需要维护一套系统&#xff0c;就可以…

【核磁共振成像】相位差重建

目录 一、相位差map重建一般步骤和反正切函数主值范围二、反正切运算三、可预期相位误差和伴随场的校正四、图形变形校正 一、相位差map重建一般步骤和反正切函数主值范围 MRI是一个相敏成像模态&#xff0c;MR原始数据傅里叶变换后的复数图像中每个像素值有模和相位。标准模重…

HTML5

写在前面 一、开个头 安装vscode 1.1 什么是网页 网站是指因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要通过浏览器来阅读。 网页是构成网站的基本元素…

uniapp 微信小程序 锚点跳转

uniapp文档 以下是我遇到的业务场景&#xff0c;是点击商品分类的某一类 然后页面滚动至目标分类&#xff0c; 首先第一步是设置锚点跳转的目的地&#xff0c;在目标的dom上面添加id属性 然后给每个分类每一项添加点击事件&#xff0c;分类这里的item数据里面有一字段是和上…

OpenCV(三):Mat类数据的读取

目录 1.Mat类矩阵的常用属性 2.Mat元素的读取 1.at方法读取Mat矩阵元素 at (int row,int col) 2.矩阵元素地址定位方式访问元素 3.Android jni demo 1.Mat类矩阵的常用属性 下面是一些Mat类的常用属性&#xff1a; rows: 返回Mat对象的行数。 cols: 返回Mat对象的列数。 …

SpringMVC概述与简单使用

1.SpringMVC简介 SpringMVC也叫做Spring web mvc,是 Spring 框架的一部分&#xff0c;是在 Spring3.0 后发布的。 2.SpringMVC优点 1.基于 MVC 架构 基于 MVC 架构&#xff0c;功能分工明确。解耦合&#xff0c; 2.容易理解&#xff0c;上手快&#xff1b;使用简单。 就可以…

AttributeError: ‘ConfigDict‘ object has no attribute ‘log_level‘

运行 python tools/train.py configs/pspnet/pspnet_r50-d8_512x512_80k_ade20k.py 时出现 问题 Traceback (most recent call last):File "tools/train.py", line 242, in <module>main()File "tools/train.py", line 167, in mainlogger get_ro…

0基础学习VR全景平台篇 第94篇:智慧景区浏览界面介绍

一、景区详细信息介绍 点击左上角的图标就可以看到景区详细信息例如景区简介&#xff0c;地址&#xff0c;开放信息&#xff0c;联系电话等 二、问题反馈中心 点击左下角的【问题反馈】按钮向作者进行问题反馈 三、开场地图 1、直接点击开场地图页面上的图标浏览该场景 2、通…

Java流数据

流数据 可以从不同的角度对流进行分类&#xff1a; 处理的数据单位不同&#xff0c;可分为&#xff1a;字符流&#xff0c;字节流 2.数据流方向不同&#xff0c;可分为&#xff1a;输入流&#xff0c;输出流 3.功能不同&#xff0c;可分为&#xff1a;节点流&#xff0c;处理…

【golang】简单介绍下goroutine

前面的两篇&#xff0c;从相对比较简单的锁的内容入手(也是干货满满)&#xff0c;开始了go的系列。这篇开始&#xff0c;进入更核心的内容。我们知道&#xff0c;go应该是第一门在语言层面支持协程的编程语言(可能是我孤陋寡闻)&#xff0c;goroutine也完全算的上是go的门面。g…

【微服务】服务发现和管理技术框架选型调研

选型背景 方案对比 结论 结合实际业务和开发需要&#xff0c;着重考虑性能可靠性、功能和社区支持程度三方面&#xff0c;认为Nacos更适合作为服务发现和管理的技术框架。具体理由如下&#xff1a; 性能更好&#xff0c;可靠性更高 经过阿里、APISIX、SpringCloudAlibaba,阿…

opencv案例06-基于opencv图像匹配的消防通道障碍物检测与深度yolo检测的对比

基于图像匹配的消防通道障碍物检测 技术背景 消防通道是指在各种险情发生时&#xff0c;用于消防人员实施营救和被困人员疏散的通道。消防法规定任何单位和个人不得占用、堵塞、封闭消防通道。事实上&#xff0c;由于消防通道通常缺乏管理&#xff0c;导致各种垃圾&#xff0…

1688API技术解析,实现关键词搜索淘宝商品(商品详情接口等)批量获取,可高并发

要使用1688API接口采集商品详情&#xff0c;可以按照以下步骤进行&#xff1a; 获取API接口权限&#xff1a;申请1688的app key和app secret&#xff0c;并获取access_token。 编写API请求代码&#xff1a;使用Python等编程语言&#xff0c;编写API请求代码。以下是一个Python…

【陈老板赠书活动 - 11期】- 【MySQL从入门到精通】

![ 陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍…

【数据分享】2006-2021年我国省份级别的集中供热相关指标(免费获取\20多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国省份级别的市政设施水平相关指标、2006-2021年我国省份级别的各类建设用地面积数…

无涯教程-Android - ToggleButton函数

ToggleButton将已选中/未选中状态显示为按钮。它基本上是一个带有指示灯的开/关按钮。 Toggle Button ToggleButton属性 以下是与ToggleButton控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。 Sr.No.Attribute…

【高性能计算】opencl语法及相关概念(四):结合opencv进行图像高斯模糊处理

目录 高斯模糊简介主函数&#xff1a;host端设备端函数&#xff1a;mywork.cl效果图对比 高斯模糊简介 高斯模糊是一种常用的图像处理技术&#xff0c;用于减少图像中的噪点和细节&#xff0c;并实现图像的平滑效果。它是基于高斯函数的卷积操作&#xff0c;通过对每个像素周围…

单片机-如何让数码管动态显示

数码管硬件图 1、数码管 连接 74HC245 芯片 单片机IO口输出难稳定&#xff0c;需要数码管与单片机连接需要增加驱动电路&#xff0c; 使用 74HC245 abcdefgDP并联导出 74HC245 对数码管进行驱动&#xff0c;P0 是输出电流 来驱动各个段的 驱动芯片 增加电阻 是为了防止电流…

WPF实战项目十四(API篇):登录注册接口

1、新建UserDto.cs public class UserDto : BaseDto{private string userName;/// <summary>/// 用户名/// </summary>public string UserName{get { return userName; }set { userName value;OnPropertyChanged(); }}private string account;/// <summary>…