【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…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...

密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...