【HeadFirst 设计模式】装饰者模式的C++实现
一、案例背景
Starbuzz是以扩张速度最快而闻名的咖啡连锁店。如果你在街角看到它的店,在对面街上肯定还会看到另一家。因为扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原先的类设计是这样的……

购买咖啡时,可以要求在其中加入各种调料,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴克会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。这是他们的第一个尝试……

很明显,Starbuzz为自己制造了一个维护噩梦:如果牛奶的价格上扬怎么办?新增一种焦糖调料风味时怎么办?
二、案例分析
看到这么多类时你肯定也会被震惊到……那么问题来了,如何进行改进呢?一个直截了当的解决方案是利用实例变量和继承,就可以追踪这些调料。比如我们在基类中加上实例变量,这些布尔值代表是否加上该调料(牛奶,豆浆,摩卡,奶泡……):
#include <iostream>
#include <string>
using namespace std;class Beverage
{
private:string description {};bool milk {};bool soy {};bool mocha {};bool whip {};public:const string getDiscription(){return description;};void setDescription(const string& description){this->description = description + "(Add " + (milk ? "Milk " : "") + (soy ? "& Soy " : "") + (mocha ? "& Mocha " : "") + (whip ? "& Whip " : "") + ")";}virtual const float cost(){return (milk ? 1 : 0) + (soy ? 2 : 0) + (mocha ? 1 : 0) + (whip ? 1.5 : 0);}const bool hasMilk() const{return milk;};void setMilk(const bool value){milk = value;};const bool hasSoy() const{return soy;};void setSoy(const bool value){soy = value;};const bool hasMocha() const{return mocha;};void setMocha(const bool value){mocha = value;};const bool hasWhip() const{return whip;};void setWhip(const bool value){whip = value;};
};class HouseBlend : public Beverage
{
public:HouseBlend(){setMilk(true);setSoy(true);setDescription("House Blend");}const float cost() override{return 5.0 + Beverage::cost();}
};class DarkRoast : public Beverage
{
public:DarkRoast(){setMilk(true);setWhip(true);setDescription("DarkRoast");}const float cost() override{return 8.0 + Beverage::cost();}
};class Decaf : public Beverage
{
public:Decaf(){setMilk(true);setWhip(true);setSoy(true);setDescription("Decaf");}const float cost() override{return 10.0 + Beverage::cost();}
};int main()
{cout << "我点了一杯" + HouseBlend().getDiscription() << ",花了" << HouseBlend().cost() << "元"<<endl;cout << "我点了一杯" + DarkRoast().getDiscription() << ",花了" << DarkRoast().cost() << "元"<<endl;cout << "我点了一杯" + Decaf().getDiscription() << ",花了" << Decaf().cost() << "元"<<endl;return 0;
}
看起来似乎还行。但是如果将来由于原材料上涨某些调料需要上涨价钱怎么办?如果出现了新的调料呢?如果顾客想要双倍摩卡的咖啡呢?
这些变化都需要我们去直接变更源码。
开放关闭原则:类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可以搭配新的行为。这样的设计具有弹性,可以应对改变,可以接收新的功能来应对改变的需求。
让我们来看看使用装饰者模式是怎么解决问题的:


三、代码分析
这里给出相关案例的C++代码实现:
#include <iostream>
#include <string>
using namespace std;class Beverage
{
protected:string description = "unknown Beverage";public:virtual const string getDescription() const{return description;}virtual const double cost() const = 0;
};class CondimentDecorator : public Beverage
{
public:virtual const string getDescription() const = 0;
};class HouseBlend : public Beverage
{
public:HouseBlend(){description = "HouseBlend";}const double cost() const override{return 5.00;}
};class DarkRoast : public Beverage
{
public:DarkRoast(){description = "DarkRoast";}const double cost() const override{return 8.00;}
};class Decaf : public Beverage
{
public:Decaf(){description = "Decaf";}const double cost() const override{return 10.00;}
};class Milk : public CondimentDecorator
{
public:Beverage* beverage {};Milk(Beverage* beverage){this->beverage = beverage;}const string getDescription() const override{return beverage->getDescription() + " & Milk";}const double cost() const override{return beverage->cost() + 1.0;}
};class Soy : public CondimentDecorator
{
public:Beverage* beverage {};Soy(Beverage* beverage){this->beverage = beverage;}const string getDescription() const override{return beverage->getDescription() + " & Soy";}const double cost() const override{return beverage->cost() + 2.0;}
};class Mocha : public CondimentDecorator
{
public:Beverage* beverage {};Mocha(Beverage* beverage){this->beverage = beverage;}const string getDescription() const override{return beverage->getDescription() + " & Mocha";}const double cost() const override{return beverage->cost() + 2.0;}
};class Whip : public CondimentDecorator
{
public:Beverage* beverage {};Whip(Beverage* beverage){this->beverage = beverage;}const string getDescription() const override{return beverage->getDescription() + " & Whip";}const double cost() const override{return beverage->cost() + 2.0;}
};int main()
{Beverage* houseblend = new Milk(new Soy(new HouseBlend()));Beverage* darkRoast = new Milk(new Soy(new Whip(new DarkRoast())));Beverage* decaf = new Milk(new Whip(new Decaf()));// 双倍摩卡Beverage* doubleMochaCoffee = new Milk(new Soy(new Mocha(new Mocha(new HouseBlend()))));cout << "我点了一杯" + houseblend->getDescription() << ",花了" << houseblend->cost() << "元" << endl;cout << "我点了一杯" + darkRoast->getDescription() << ",花了" << darkRoast->cost() << "元" << endl;cout << "我点了一杯" + decaf->getDescription() << ",花了" << decaf->cost() << "元" << endl;cout << "我点了一杯" + doubleMochaCoffee->getDescription() << ",花了" << doubleMochaCoffee->cost() << "元" << endl;return 0;
}
相关文章:
【HeadFirst 设计模式】装饰者模式的C++实现
一、案例背景 Starbuzz是以扩张速度最快而闻名的咖啡连锁店。如果你在街角看到它的店,在对面街上肯定还会看到另一家。因为扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原先的类设计是这样的…… 购买咖啡时&am…...
大白话解释TCP的三次握手和四次挥手
你好,我是沐爸,欢迎点赞、收藏和关注。个人知乎 TCP的三次握手是浏览器与服务器建立连接的过程,而四次挥手,是两者断开连接的过程。今天把客户端和服务端当做两个人,通过打电话的方式解释连接建立和断开的过程。 TCP…...
asyncua模块实现OPC UA通讯
asyncua是OPCUA的python实现,使用起来非常方便,其github地址是https://github.com/FreeOpcUa/opcua-asyncio UaExpert是OPC UA Client的GUI工具,当编写好server代码后并运行,我们可以使用UaExpert去和server进行通信。UaExpert使…...
RabbitMQ的核心概念
RabbitMQ是一个消息中间件,也是一个生产者消费者模型,负责接收,存储和转发消息。 核心概念 Producer 生产者,是RabbitMQ Server的客户端,向RabbitMQ发送消息。 Consumer 消费者,是RabbitMQ Server的客…...
【vSphere 7/8】深入浅出 vSphere 证书 Ⅰ—— 初识和了解 vSphere证书
目录 摘要1. vSphere 安全证书1.1 vSphere 安全证书的类型和有效期 2. 在 vSphere Client 中初识 vSphere 证书2.1 vCenter 8.0.3 的 vSphere Client 界面2.2 vCenter Server 7.0 Update2 到 vCenter Server 8.0 Update 2 的 vSphere Client 界面2.3 vCenter Server 7.0 到 vCe…...
【云备份】服务端模块-热点管理
文章目录 0.回顾extern1.介绍2.实现思想3.代码测试代码 热点管理总结 0.回顾extern extern cloudBackup::DataManager *_dataManager extern 关键字用于声明一个全局变量或对象,而不定义它。这意味着 _dataManager 是一个指向 cloudBackup::DataManager 类型的指针…...
call apply bind特性及手动实现
call // 原生的call var foo { value: 1 };function bar(...args) {console.log("this", this.value, args); }bar.call(foo)// call 改变了bar的this指向 // bar函数执行了 // 等价于 // var foo { // name: "tengzhu", // sex: "man", …...
pygame开发课程系列(5): 游戏逻辑
第五章 游戏逻辑 在本章中,我们将探讨游戏开发中的核心逻辑,包括碰撞检测、分数系统和游戏状态管理。这些元素不仅是游戏功能的关键,还能显著提升游戏的趣味性和挑战性。 5.1 碰撞检测 碰撞检测是游戏开发中的一个重要方面,它用…...
嵌入式系统实时任务调度算法优化与实现
嵌入式系统实时任务调度算法优化与实现 目录 嵌入式系统实时任务调度算法优化与实现 引言 1.1 嵌入式系统的重要性 1.2 实时任务调度的重要性 实时任务的定义与分类 2.1 实时任务的定义 2.2 实时任务的分类 2.3 实时任务的其他分类方法 硬实时与软实时系统 3.1 硬实…...
Java:枚举转换
在Java中,你可以使用Enum.valueOf()方法将字符串转换为枚举常量。但是,如果你想要将枚举转换为其他类型,你需要自定义转换方法。以下是一个简单的例子,演示如何将枚举转换为整数: public enum Color {RED(1), GREEN(2…...
Vue、react父子组件生命周期
Vue 的父子组件生命周期 以下分为三部分,加载渲染阶段——更新阶段——销毁阶段,我们来一一介绍: 1、加载渲染阶段 在加载渲染阶段,一定得等子组件挂载完毕后,父组件才能挂载完毕,所以父组件的 mounted 在…...
HTML 基础要素解析
目录 HTML 初步认识 纯文本文件介绍 纯文本文件与其它文件的区别 Html介绍 HTML 骨架 文档类型(!DOCTYPE)声明 介绍 常用的 DOCTYPE 声明 meta标签 字符集 关键字和页面描述 HTML 初步认识 纯文本文件介绍 纯文本文件指的是仅包含文本内容&am…...
开源的向量数据库Milvus
Milvus是一款开源的向量数据库,专为处理向量搜索任务而设计,尤其擅长处理大规模向量数据的相似度检索。 官网地址:https://milvus.io/ 以下是关于Milvus的详细介绍: 一、基本概念 向量数据库:Milvus是一款云原生向量…...
设计模式-工厂方法
“对象创建”模式 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。典型模式 Factory MethodAbstract Factory…...
Flask SQLALchemy 的使用
Flask SQLALchemy 的使用 安装 Flask-SQLAlchemy配置 Flask-SQLAlchemy定义模型创建数据库和表插入和查询数据更新和删除数据迁移数据库总结Flask-SQLAlchemy 是一个 Flask 扩展,它简化了 Flask 应用中 SQLAlchemy 的使用。SQLAlchemy 是一个强大的 SQL 工具包和对象关系映射(…...
Metasploit漏洞利用系列(一):MSF完美升级及目录结构深度解读
在信息安全领域,MetasploitFramework(MSF)是一个无处不在的工具,它集合了大量的渗透测试和漏洞利用模块,帮助安全专家识别和利用系统中的弱点。本文将深入探讨如何对Metasploit进行完美升级,以及对其核心目…...
C/C++|经典代码题(动态资源的双重释放与「按值传递、按引用传递、智能指针的使用」)
以下代码中你能看出其存在什么问题?如何修复,能给出几种方法?分别在什么场景下用哪种方法。 #include <iostream>class Buffer {public:Buffer() { std::cout << "Buffer created" << std::endl; }~Buffer() { s…...
西北乱跑娃 -- linux使用笔记
1.后台运行每天一个日志文件 nohup python3.8 manage.py >> $(date %Y-%m-%d).log 2>&1 &2.目录操作: ls:列出目录内容。cd:改变当前工作目录。pwd:显示当前工作目录的路径。mkdir:创建新目录。rmd…...
Kubectl基础命令使用
一.Kubectl 基础命令 格式: kubectl [command] [TYPE] [NAME] [FLAGS] kubectl 是 Kubernetes 的命令行工具,用于管理 Kubernetes 集群。以下是一些常用的 kubectl 命令及其选项: 常用命令 获取资源 列出所有资源类型(Pods、De…...
推荐编译器插件:Fitten Code 更快更好的AI助手
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
个人------完成主页,个人花园,相册页面的前端代码编写
最近一段时间,我专注于个人花园博客系统的前端开发工作,从基础页面搭建到交互逻辑实现,逐步完成了多个核心模块的开发,虽然目前尚未连接后端,但前端页面的视觉呈现和基础交互已全部落地。本次开发主要围绕个人主页、相…...
解锁论文写作新姿势:书匠策AI,你的期刊论文智囊团
在学术的浩瀚海洋中,每一位探索者都渴望拥有一盏明灯,照亮前行的道路。对于广大教育领域的学者、研究生乃至本科生而言,撰写一篇高质量的期刊论文不仅是学术能力的体现,更是通往更高学术殿堂的钥匙。然而,面对繁琐的选…...
告别卡顿!用EnhancedScroller优化Unity UI长列表的完整避坑指南
告别卡顿!用EnhancedScroller优化Unity UI长列表的完整避坑指南 在Unity开发中,处理大量数据的UI列表是常见的需求,但原生ScrollRect在面对成千上万条数据时往往力不从心。想象一下,当用户滑动一个包含数百个好友的社交列表时&am…...
实战驱动:基于快马平台生成集成openclaw的ubuntu自动化测试项目实例
在自动化测试和数据抓取领域,openclaw凭借其强大的浏览器控制能力成为开发者的得力助手。最近我在一个电商价格监控项目中需要快速搭建环境,发现通过InsCode(快马)平台可以轻松生成包含完整环境配置和实战示例的项目模板,这里分享下我的实践过…...
2025届学术党必备的十大AI科研方案推荐榜单
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在当下的学术与内容创作范畴之内,对于AI生成文本的检测变得越发严格起来。降AI率…...
从内置函数到自定义算法:用 AMDP 驱动的 CDS Scalar Function 打开 ABAP CDS 的新扩展面
在很多 ABAP CDS 项目里,开发者都会遇到一个很现实的问题:系统预置函数够用,但不总是刚好够用。简单的数值换算、字符串处理、日期推导,内置能力通常已经覆盖;可一旦业务进入更复杂的区间,例如分摊比例计算、复合折扣推导、动态计费规则、评分算法封装,单纯依赖 CDS 表达…...
如何用AI润色简历?2026年分步指南与实用技巧
在2026年的求职市场中,简历是连接你与心仪岗位的第一座桥梁。面对日益智能化的招聘系统(ATS)和快节奏的筛选流程,仅凭一份通用简历已难以脱颖而出。这时,AI润色简历从一种新兴尝试转变为高效、精准的必备策略。本文旨在…...
悟空率先接入国产最强编程模型Qwen3.6-Plus
4月2日,阿里巴巴正式发布新一代大语言模型Qwen3.6-Plus,阿里在企业级市场的旗舰AI应用悟空率先完成接入。Qwen3.6-Plus在代码、智能体、推理、原生多模态等能力上整体性能大幅增强,在智能体编程SWE-bench系列评测、真实世界智能体任务Claw-Ev…...
11,2kw双向储能变换器:基于PFCLLC结构的工业应用仿真研究
11,2kw双向储能变换器仿真,已工业应用。 pfcllc结构,可整流,可逆变。 整流模式下,pfc为单相pwm整流器,输入电压220V,50Hz,llc输出电压55V。 逆变模式下,llc输入电压55V&a…...
别再让广播闪退!Android 14广播安全新规RECEIVER_EXPORTED的保姆级避坑指南
Android 14广播安全新规:RECEIVER_EXPORTED的深度解析与实战指南 去年秋天,当Google正式发布Android 14时,许多开发者发现原本运行良好的广播注册代码突然开始抛出SecurityException。这个看似简单的API变更背后,其实是Android团队…...
