【设计模式】装饰模式

server/2025/3/22 18:08:55/

六、装饰模式

装饰(Decorator) 模式也称为装饰器模式/包装模式,是一种结构型模式。这是一个非常有趣和值得学习的设计模式,该模式展现出了运行时的一种扩展能力,以及比继承更强大和灵活的设计视角和设计能力,甚至在有些场合下,不使用该模式很难解决问题。
在本模式的讲解过程中,会提及类与类之间的继承关系和组合关系,还会引出面向对象程序设计的一个重要原则——组合复用原则。

6.1 问题的提出

继续前面的闯关打斗类游戏。在游戏中,不但会出现各种人物、怪物,还会出现许多UI (用户接口)界面。例如,主角一般会随身携带背包,背包中的每个格子用于放置一个物品,根据策划的规定,背包中格子数量比较多时,还会在背包右侧显示出滚动条,如图6.1所示。

在这里插入图片描述

再例如,有时需要显示一些公告信息,如图6.2所示。当公告信息太长时,也可能会出现滚动条等。
图6.1和图6.2这些看得见的UI界面元素称为控件。例如,要向其中输入文字可以使用文本控件,要显示多行信息可以使用列表控件等,当要显示的内容过长或者过宽时会显示出滚动条控件。

在这里插入图片描述

这里就以一个最简单的控件——列表控件为例,说明如何丰富该控件上所显示的内容,图6.3中第1幅子图是一个普通的列表控件,第2幅图增加了边框让其更有立体感,第3幅图增加了一个垂直滚动条,而第4幅图又增加了一个水平滚动条。

在这里插入图片描述

传统继承方案的问题

  1. 子类膨胀:新增功能(如阴影、外发光)需不断创建子类。
  2. 灵活性差:无法灵活组合功能(如无框但有垂直滚动条的控件)。

解决方案思路

采用组装方式动态添加功能:

  1. 基础控件(ListCtrl)作为核心。
  2. 附加功能(边框、滚动条)作为装饰器,通过组合方式叠加。

6.2 引入装饰模式

组合复用原则(CRP)

核心思想:优先使用组合而非继承,以降低类间耦合,避免父类代码冗余。

装饰模式实现代码

1. 抽象构件(Control)
// 抽象的控件类
class Control {
public:virtual void draw() = 0;
public:virtual ~Control() {}
};
2. 具体构件(ListCtrl)
// 列表控件类
class ListCtrl : public Control {
public:virtual void draw() {cout << "  绘制普通的列表控件!" << endl;}
};
3. 抽象装饰器(Decorator)
// 抽象的装饰器类
class Decorator : public Control {
public:Decorator(Control* tmpctrl) : m_control(tmpctrl) {}  // 组合关系virtual void draw() {m_control->draw();  // 委托给被装饰对象}
private:Control* m_control;
};
4. 具体装饰器

边框装饰器

class BorderDec : public Decorator {
public:BorderDec(Control* tmpctrl) : Decorator(tmpctrl) {}virtual void draw() {Decorator::draw();  // 先绘制原内容drawBorder();       // 再绘制新增内容}
private:void drawBorder() { cout << "  绘制边框!" << endl; }
};

垂直滚动条装饰器

class VerScrollBarDec : public Decorator {
public:VerScrollBarDec(Control* tmpctrl) : Decorator(tmpctrl) {}virtual void draw() {Decorator::draw();drawVerScrollBar();}
private:void drawVerScrollBar() { cout << "  绘制垂直滚动条!" << endl; }
};

客户端代码示例

int main() {// 组装带边框和垂直滚动条的控件Control* base = new ListCtrl();Control* withBorder = new BorderDec(base);Control* final = new VerScrollBarDec(withBorder);final->draw();  // 输出:普通列表 → 边框 → 垂直滚动条cout << "------------------------" << endl;// 组装只带水平滚动条的控件Control* base2 = new ListCtrl();Control* withHor = new HorScrollBarDec(base2);withHor->draw();  // 输出:普通列表 → 水平滚动条// 释放资源(注意顺序)delete final;delete withBorder;delete base;delete withHor;delete base2;return 0;
}

装饰模式UML图

m_control
«abstract»
Control
+draw()
ListCtrl
+draw()
«abstract»
Decorator
-m_control: Control
+draw()
BorderDec
+draw()
-drawBorder()
VerScrollBarDec
+draw()
-drawVerScrollBar()
HorScrollBarDec
+draw()
-drawHorScrollBar()

模式角色

角色说明示例类
抽象构件定义统一接口Control
具体构件基础功能实现ListCtrl
抽象装饰器持有构件引用,定义装饰接口Decorator
具体装饰器实现具体装饰逻辑BorderDec

6.3 饮料价格计算范例

问题描述

  • 基础饮料:10元
  • 可选配料:砂糖(+1元)、牛奶(+2元)、珍珠(+2元)

实现代码

1. 抽象构件(Beverage)
class Beverage {
public:virtual int getPrice() = 0;virtual ~Beverage() {}
};
2. 具体构件(FruitBeverage)
class FruitBeverage : public Beverage {
public:int getPrice() override { return 10; }
};
3. 抽象装饰器(CondimentDecorator)
class CondimentDecorator : public Beverage {
protected:Beverage* beverage;
public:CondimentDecorator(Beverage* b) : beverage(b) {}
};
4. 具体装饰器

砂糖装饰器

class Sugar : public CondimentDecorator {
public:Sugar(Beverage* b) : CondimentDecorator(b) {}int getPrice() override { return beverage->getPrice() + 1; }
};

珍珠装饰器

class Bubble : public CondimentDecorator {
public:Bubble(Beverage* b) : CondimentDecorator(b) {}int getPrice() override { return beverage->getPrice() + 2; }
};

客户端代码

int main() {Beverage* drink = new FruitBeverage();drink = new Bubble(drink);  // 加珍珠(+2)drink = new Sugar(drink);   // 加砂糖(+1)cout << "总价格:" << drink->getPrice() << "元" << endl;  // 输出:13元delete drink;return 0;
}

饮料范例UML图

beverage
«abstract»
Beverage
+getPrice()
FruitBeverage
+getPrice()
«abstract»
CondimentDecorator
-beverage: Beverage
+getPrice()
Sugar
+getPrice()
Bubble
+getPrice()

总结:装饰模式通过组合实现动态功能扩展,避免继承导致的类爆炸,符合开闭原则,适用于需要灵活添加可选功能的场景。


http://www.ppmy.cn/server/177110.html

相关文章

如何构建简单有效的AI Agents代理?

工程技术 在过去的一年里&#xff0c;我们与数十个跨行业的团队合作&#xff0c;构建基于大型语言模型&#xff08;LLM&#xff09;的代理。我们发现&#xff0c;最成功的实现并不是使用复杂的框架或专门的库&#xff0c;而是采用简单、可组合的模式。 在本文中&#xff0c;我…

Compose 实践与探索十六 —— 与传统的 View 系统混用

Compose 发展初期的几年&#xff0c;会是新的模块用 Compose 写&#xff0c;然后逐渐的把老界面从 View 替换成 Compose 组件&#xff0c;直到全部或几乎全部是 Compose 代码的模式。 原生的 SurfaceView 与 TextureView 的重点是在它们底层的 Surface API&#xff0c;而不是 V…

浅谈跨平台框架的演变(H5混合开发->RN->Flutter)

引言 这里分为四个阶段&#xff1a; 第一阶段 &#xff1a; 原生开发 第二阶段 &#xff1a; H5混合开发 第三阶段&#xff1a; 跨平台RN 第四阶段&#xff1a; 跨平台Flutter 正文 第一阶段&#xff1a; 原生开发 开发成本比较大 &#xff1a; 需要Android 和ios 开发两…

django设置admin的排列顺序,耗3小时【躲坑指南】

django 项目中&#xff0c;这个数据栏目的显示排列顺序我希望更贴近业务 比如要让【商品货品信息】中的9个数据表根据人为规定来进行排序 结果&#xff1a;工程量很大。 能够实现人为的自定义排序 最简单的设置就是给模型添加号数标记 主应用中创建admin–设置了其中一个应用…

Pytorch中的torch.utils.data.Dataset 类

1、使用方法 from torch.utils.data import Dataset 2、torch.utils.data.Dataset 类的定义 class Dataset(Generic[_T_co]):r"""An abstract class representing a :class:Dataset.All datasets that represent a map from keys to data samples should sub…

油候插件、idea、VsCode插件推荐(自用)

开发软件&#xff1a; 之前的文章&#xff1a; 开发必装最实用工具软件与网站 推荐一下我使用的开发工具 目前在用的 油候插件 AC-baidu-重定向优化百度搜狗谷歌必应搜索_favicon_双列 让查询变成多列&#xff0c;而且可以流式翻页 Github 增强 - 高速下载 github下载 TimerHo…

基于ArcGIS和ETOPO-2022 DEM数据分层绘制全球海陆分布

第〇部分 前言 一幅带有地理空间参考、且包含海陆分布的DEM图像在研究区的绘制中非常常见&#xff0c;本文将实现以下图像的绘制 关键步骤&#xff1a; &#xff08;1&#xff09;NOAA-NCEI官方下载最新的ETOPO-2022 DEM数据 &#xff08;2&#xff09;在ArcGIS&#xff08;…

JavaIO流的使用和修饰器模式(直击心灵版)

系列文章目录 JavaIO流的使用和修饰器模式 文章目录 系列文章目录前言一、字节流&#xff1a; 1.FileInputStream(读取文件)2.FileOutputStream(写入文件) 二、字符流&#xff1a; 1..基础字符流:2.处理流&#xff1a;3.对象处理流&#xff1a;4.转换流&#xff1a; 三、修饰器…