装饰模式(decorator-pattern)

news2024/9/20 10:40:44

装饰模式(decorator-pattern)

文章目录

  • 装饰模式(decorator-pattern)
    • 一、手抓饼点餐系统
    • 二、要求进阶
    • 三、装饰模式概要
    • 四、装饰模式的优劣及应用场景
      • 1. 优点
      • 2.缺点
      • 3.应用场景

一、手抓饼点餐系统

请设计一个手抓饼点餐系统,支持加配菜,比如里脊、肉松、火腿等,并提供接口,获取加了哪些配菜,和最后的总价。

听起来很简单嘛,代码敲起来:

"ingredient.hpp",用来定义各种配料。

class Ingredient {
public:
  virtual std::string get_description() = 0;
  virtual double get_price() = 0;

protected:
  std::string description_;
  double price_;
};

class Ham : public Ingredient {
public:
  Ham() {
    description_ = "火腿";
    price_ = 1.0;
  }

  std::string get_description() {
    return description_;
  }

  double get_price() {
    return price_;
  }
};

class Tenderloin : public Ingredient {
public:
  Tenderloin() {
    description_ = "里脊肉";
    price_ = 2.0;
  }

  std::string get_description() {
    return description_;
  }

  double get_price() {
    return price_;
  }
};

class PorkFloss : public Ingredient {
public:
  PorkFloss() {
    description_ = "肉松";
    price_ = 1.5;
  }

  std::string get_description() {
    return description_;
  }

  double get_price() {
    return price_;
  }
};

"shredded_cake.hpp",定义手抓饼。

class ShreddedCake {
public:
  ShreddedCake() : description_("饼, 单价3元\n"), total_price_(3.0) {}

  void add_ingredient(Ingredient *ingredient) {
    description_ += ingredient->get_description() + "\n";
    total_price_ += ingredient->get_price();
  }

  std::string get_description() {
    return description_;
  }

  double get_total_price() {
    return total_price_;
  }

private:
  std::string description_;
  double total_price_;
};

最后的测试:

int main() {
  ShreddedCake shredded_cake;
  std::shared_ptr<Ham> ham = std::make_shared<Ham>();
  std::shared_ptr<Tenderloin> tenderloin = std::make_shared<Tenderloin>();
  std::shared_ptr<PorkFloss> pork_floss = std::make_shared<PorkFloss>();

  shredded_cake.add_ingredient(ham.get());
  shredded_cake.add_ingredient(tenderloin.get());
  shredded_cake.add_ingredient(pork_floss.get());

  std::cout << "手抓饼配料: \n" << shredded_cake.get_description() << std::endl;
  std::cout << "总价: " << shredded_cake.get_total_price() << std::endl;
  return 0;
}
// output
手抓饼配料: 
饼, 单价3元
火腿, 单价1元
里脊肉, 单价2元
肉松, 单价1.5元

总价: 7.5

很简单的实现,对吧?

二、要求进阶

现在,店铺扩张了,不仅可以点手抓饼,还有煎饼、汤包啥的,咋办呢?

简单,写一个抽象Breakfast类,让各种早点继承它就ok了:

class BreakFast {
public:
  virtual void add_ingredient(Ingredient *ingredient) = 0;
  virtual std::string get_description() = 0;
  virtual double get_total_price() = 0;

protected:
  std::string description_;
  double total_price_;
};

class ShreddedCake : public BreakFast {
public:
  ShreddedCake() {
    description_ = "饼, 单价3元\n"; 
    total_price_ = 3.0;
  } 

  void add_ingredient(Ingredient *ingredient) {
    description_ += ingredient->get_description() + "\n";
    total_price_ += ingredient->get_price();
  }

  std::string get_description() {
    return description_;
  }

  double get_total_price() {
    return total_price_;
  }

};

class Pancake : public BreakFast {
public:
  Pancake() {
    description_ = "煎饼, 单价2元\n"; 
    total_price_ = 2.0;
  } 

  void add_ingredient(Ingredient *ingredient) {
    description_ += ingredient->get_description() + "\n";
    total_price_ += ingredient->get_price();
  }

  std::string get_description() {
    return description_;
  }

  double get_total_price() {
    return total_price_;
  }

};

class SteamedDumpling : public BreakFast {
public:
  SteamedDumpling() {
    description_ = "汤包, 单价7元\n"; 
    total_price_ = 7.0;
  } 

  std::string get_description() {
    return description_;
  }

  double get_total_price() {
    return total_price_;
  }

};

但这样一份代码有一个尴尬的问题:

  1. SteamedDumpling,即汤包类,没有重写抽象方法add_ingredient,虽然编译通过但是不能正常使用这个类。
  2. 但是,汤包在现实生活中,也不需要add_ingredient,即加配料。

怎么办呢?一个办法,就是为需要加配料的早餐添加add_ingredient方法,但是有没有什么更优雅的解决方案呢?

我们即将引入装饰模式的概念。

三、装饰模式概要

image-20230119134802024

  • Abstract BaseClass
    • 抽象基类
    • 提供抽象方法Operation
  • Concrete DerivedClass
    • 派生于抽象基类,用来描述具体的对象特性
    • 重写抽象方法Operation(纯虚方法)
  • Decorator
    • 装饰抽象类,派生于抽象基类,用于装饰Concrete DerivedClass
  • Concrete Decorator
    • 重写抽象方法Operation
    • 可以添加自己私有的成员变量和成员函数,用于给Operation扩展新功能

听起来非常抽象,我们改写代码后进行分析:

"breakfast.hpp"

class Breakfast {
public:
  virtual std::string get_description() = 0;
  virtual double get_price() = 0;

protected:
  std::string description_;
  double price_;
};

class ShreddedCake : public Breakfast {
public:
  ShreddedCake() {
    description_ = "手抓饼, 单价3元"; 
    price_ = 3.0;
  } 

  std::string get_description() {
    return description_ + "\n";
  }

  double get_price() {
    return price_;
  }

};

class Pancake : public Breakfast {
public:
  Pancake() {
    description_ = "煎饼, 单价2元"; 
    price_ = 2.0;
  } 

  std::string get_description() {
    return description_ + "\n";
  }

  double get_price() {
    return price_;
  }

};

class SteamedDumpling : public Breakfast {
public:
  SteamedDumpling() {
    description_ = "汤包, 单价7元"; 
    price_ = 7.0;
  } 

  std::string get_description() {
    return description_ + "\n";
  }

  double get_price() {
    return price_;
  }

};

这是早餐类的继承体系,基类是Breakfast,提供两个纯虚函数,派生出的三个子类重写这两个函数。

  • Breakfast对应Abstract BaseClass
  • ShreddedCake、Pancake和SteamedDumpling对应Concrete DerivedClass

"ingredient.hpp"

class Ingredient : public Breakfast {
protected:
  Breakfast *breakfast_;
};

class Ham : public Ingredient {
public:
  Ham(Breakfast *breakfast) {
    breakfast_ = breakfast;
    description_ = "火腿, 单价1元";
    price_ = 1.0;
  }

  std::string get_description() {
    return breakfast_->get_description() + description_ + "\n";
  }

  double get_price() {
    return breakfast_->get_price() + price_;
  }
};

class Tenderloin : public Ingredient {
public:
  Tenderloin(Breakfast *breakfast) {
    breakfast_ = breakfast;
    description_ = "里脊肉, 单价2元";
    price_ = 2.0;
  }

  std::string get_description() {
    return breakfast_->get_description() + description_ + "\n";
  }

  double get_price() {
    return breakfast_->get_price() + price_;
  }
};

class PorkFloss : public Ingredient {
public:
  PorkFloss(Breakfast *breakfast) {
    breakfast_ = breakfast;
    description_ = "肉松, 单价1.5元";
    price_ = 1.5;
  }

  std::string get_description() {
    return breakfast_->get_description() + description_ + "\n";
  }

  double get_price() {
    return breakfast_->get_price() + price_;
  }
};

这是配料类的继承体系,Ingredient继承自Breakfast,由于没有重写纯虚函数,因此也是抽象类。

派生出的三个子类重写了get_descriptionget_price两个纯虚函数。

  • Ingredient对应装饰基类Decorator
  • Tenderloin、PorkFloss对应Concrete Decorator,即具体的装饰子类。
    • 由于是装饰类,因此它们可以有自己的方法和变量,其中不可或缺的是一个指向父类的Breakfast指针
    • 通过指向父类的Breakfast指针,装饰类可以获得父类的特性,并且将自己的新特性追加过去。
      • 比如装饰类中重写get_price:先使用breakfast指针获取父类的价格,再将自身的价格追加过去。

"main.cpp"

int main() {
  try {
    std::shared_ptr<Breakfast> breakfast = std::make_shared<Pancake>();
    std::shared_ptr<Breakfast> add_1_ingredient = std::make_shared<Ham>(breakfast.get());
    std::shared_ptr<Breakfast> add_2_ingredient  = std::make_shared<Tenderloin>(add_1_ingredient.get());
    std::shared_ptr<Breakfast> add_3_ingredient  = std::make_shared<PorkFloss>(add_2_ingredient.get());

    std::cout << "配料: \n" << add_3_ingredient->get_description() << std::endl;
    std::cout << "总价: " << add_3_ingredient->get_price() << "元" << std::endl << std::endl;
  } catch (const std::exception &e) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}
  1. 首先,让breakfast指向一个Pancake,表示要点一个煎饼。

  2. 其次,new一个Ham,并将刚刚的基类指针传过去,表示要追加一个火腿。

  3. Tenderloin和PorkFloss同理。

  4. 加入我还要加一份肉松,那么可以添一句:

    std::shared_ptr<Breakfast> add_4_ingredient  = std::make_shared<PorkFloss>(add_3_ingredient.get());
    

四、装饰模式的优劣及应用场景

1. 优点

  1. 可以在不修改原先类的基础上给它追加一些新的特性,减少被装饰类的复杂程度。
  2. 相比于纯粹的继承体系,装饰可以通过用户主动的包装完成功能的复合叠加,而继承则需要老老实实地写多个类。假如有4个功能,则一共有2^4-1=15种复合情况,使用装饰模式只需要写4种装饰类,而继承则需要完成15个类(功能更多会导致指数爆炸!)

2.缺点

1. 会导致代码更加复杂,出现问题时排查难度加大。

3.应用场景

就像装饰模式的优点那样,如果你想动态地增加原有类的特性,那么装饰模式再合适不过了。

以Java的IO流举例:image-20230119184230465

InputStream中的FilterInputStream就是一个装饰基类,为其它的IO流类提供缓冲的功能。如果这里用继承,那么会有多少个类呢…想想都离谱!

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

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

相关文章

C++ STL

目录 1.STL诞生 2.STL概念 3.STL六大主件 4.STL容器 算法 迭代器 5.容器算法迭代器初识&#xff0c;vector 5.1vector存放内置数据类型&#xff0c; 5.2vector存放自定义数据类型&#xff0c;解引用.访问&#xff0c;指针->访问&#xff0c;存放自定义数据类型指针。迭代器…

LeetCode(Array)1365. How Many Numbers Are Smaller Than the Current Number

1.问题 Given the array nums, for each nums[i] find out how many numbers in the array are smaller than it. That is, for each nums[i] you have to count the number of valid j’s such that j ! i and nums[j] < nums[i]. Return the answer in an array. Examp…

多目标建模总结

1. 概述 在推荐系统中&#xff0c;通常有多个业务目标需要同时优化&#xff0c;常见的指标包括点击率CTR、转化率CVR、 GMV、浏览深度和品类丰富度等。为了能平衡最终的多个目标&#xff0c;需要对多个目标建模&#xff0c;多目标建模的常用方法主要可以分为&#xff1a; 多模…

Linux常用命令——top命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) top 显示或管理执行中的程序 补充说明 top命令可以实时动态地查看系统的整体运行情况&#xff0c;是一个综合了多方信息监测系统性能和运行信息的实用工具。通过top命令所提供的互动式界面&#xff0c;用热键可…

C primer plus学习笔记 —— 13、存储类别、内存管理

文章目录存储类别定义、声明和初始化的区别作用域翻译单元和文件链接属性存储期存储类别多文件共享全局变量函数的存储类别存储类别的选择分配内存&#xff08;malloc、free&#xff09;malloc和calloc创建数组方式free的重要性举例存储类别 int a 1; int *p &a; int ra…

【Stm32野火】:野火STM32F103指南者开发板烧写官方示例程序LCD无法点亮?LCD示例程序无法使用?

项目场景&#xff1a; 大家好&#xff0c;最近在使用野火STM32F103指南者开发板的时候发现官方的示例程序LCD驱动代码居然无法直接驱动LCD点亮&#xff0c;这让我百思不得其解&#xff0c;以下就是我的踩坑填坑的过程&#xff0c;希望对大家有所帮助。 野火官方资料下载文档链接…

systemd介绍

systemd是一个 Linux 系统基础组件的集合&#xff0c;提供了一个系统和服务管理器&#xff0c;运行为 PID 1 并负责启动其它程序。功能包括&#xff1a;支持并行化任务&#xff1b;同时采用 socket 式与 D-Bus 总线式激活服务&#xff1b;按需启动守护进程&#xff08;daemon&a…

与Oracle不一样的union

与Oracle不一样的union一、引言二、实验探寻union2.1 再现DM8案例2.2 再现Oracle案例2.3 实验结论一、引言 前三日&#xff0c;同事call我聊发文查询优化排序问题&#xff0c;当时联想到union自身的特性&#xff08;合并去重&#xff0c;默认排序输出结果集&#xff09;&#…

(考研湖科大教书匠计算机网络)第一章概述-第五节2:计算机网络体系结构之OSI参考模型和TCPIP参考模型

文章目录一&#xff1a;OSI参考模型&#xff08;1&#xff09;应用层&#xff08;Application Layer&#xff09;&#xff08;2&#xff09;表示层&#xff08;Presentation Layer&#xff09;&#xff08;3&#xff09;会话层&#xff08;Session Layer&#xff09;&#xff0…

STC32G 单片机系列通用定时器的用法及编程

STC32G单片机与STC15系列单片机一样有T0~T4共5个通用定时器。其功能大致相同&#xff0c;与STC15系列单片机定时器不同的是STC32G单片机的定时器每个都多了一个8位预分频器&#xff0c;如下&#xff1a;这样定时器可作为一个24位定时器使用&#xff0c;做计数器使用与分频器就没…

【Flink】浅谈Flink背压问题(1)

概述 在多线程的情况下有一个典型的模&#xff0c;型生产者消费者模型&#xff0c;该模型主要由生产者、消费者和一个大小固定的队列组成。生产者向队列发送数据&#xff0c;消费者从队列中取出数据并处理。 针对上述模型&#xff0c;如果队列属于有限长度&#xff0c;当消费者…

UE5执行Python脚本插件

1.启用UE5的Python脚本编辑器&#xff1a; 在Edit里面找到Plugins&#xff0c;然后打开插件管理器&#xff0c;搜索Python,找到 Python Editor Script Plugin并启用它。该插件也可能会自动启用&#xff08;至少我的UE5是这样的&#xff09;&#xff0c;如果已经自动启用&#…

python机器学习(一)算法学习的步骤、机器学习的应用及流程(获取数据、特征工程、模型、模型评估)

机器学习入门 机器学习中需要理论性的知识&#xff0c;如数学知识为微积分(求导过程&#xff0c;线性回归的梯度下降法)&#xff0c;线性代数(多元线性回归&#xff0c;高纬度的数据&#xff0c;矩阵等)&#xff0c;概率论(贝叶斯算法)&#xff0c;统计学(贯穿整个学习过程)&a…

Nginx使用(五)配置高可用集群示例

一、条件&#xff08;1&#xff09;需要两台Nginx服务器&#xff08;2&#xff09;需要keepalived&#xff08;3&#xff09;需要虚拟ip二、准备工作&#xff08;1&#xff09;需要两台服务器&#xff08;2&#xff09;在两台服务器安装nginx&#xff08;3&#xff09;在两台服…

Linux应用基础与实训小结

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

ElasticSearch安装及基本使用

文章目录0. 学习资料1. 概念2. 安装ESdocker安装1. **下载镜像**2. **创建实例**3. **浏览器访问&#xff1a;**4. 测试使用Kibana安装1. 下载镜像2. 创建实例3. 效果3. 检索_cat保存查询通过id查询乐观锁操作更新删除批量操作**导入测试数据**4. 进阶搜索基本检索参考文档基本…

将数据从Java Bean复制到另一个Java Bean

JavaBean复制的几种方式1&#xff1a;概述在实际编程过程中&#xff0c;我们常常要遇到这种情况&#xff1a;有一个对象A&#xff0c;在某一时刻A中已经包含了一些有效值&#xff0c;此时可能 会需要一个和A完全相同新对象B&#xff0c;并且此后对B任何改动都不会影响到A中的值…

P2T: Pyramid Pooling T ransformer for Scene Understanding

论文链接&#xff1a; https://arxiv.org/abs/2106.12011 中文版本&#xff1a; https://mmcheng.net/wp-content/uploads/2022/08/22PAMI_P2T_CN.pdf 代码链接&#xff1a; https://github.com/yuhuan-wu/P2T P2T: Pyramid Pooling T ransformer for Scene Understanding一、摘…

2023年网络安全比赛--JavaScript安全绕过中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.使用渗透机场景kali中工具扫描服务器,将服务器上apache版本号作为flag提交; 2.使用渗透机场景windows7访问服务其场景中的网站(网站路径为IP/javascript),找到网站首页中flag并提交; 3.使用渗透机场景windows7根据第二题的…

2. 拍照的基础知识

1. 单反相机的全称为单镜头反光相机 单反相机的原理就体现在“单”和“反”上&#xff0c;单字就是单镜头&#xff0c;就是说单反相机只能安装一个镜头&#xff0c;光线或是影像通过单反镜头进行来取景的。 对于“反”来说就是相机系统内部有一个反光板。光线通过单反镜头投射…