装饰模式(decorator-pattern)(代码片段)

白龙码~ 白龙码~     2023-01-20     690

关键词:

装饰模式(decorator-pattern)

文章目录

一、手抓饼点餐系统

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

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

"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方法,但是有没有什么更优雅的解决方案呢?

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

三、装饰模式概要

  • 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流举例:

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

装饰模式笔记(代码片段)

菜鸟教程连接https://www.runoob.com/design-pattern/decorator-pattern.htmlUML类图入下:在Java的IO流的类图解决的问题:想给一个类扩展功能,但是不想继承可以动态扩展功能,撤消功能(不知到体现在哪里)注意点:装饰器需要继承自被装饰者... 查看详情

装饰模式

...System;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;/*装饰模式**定义接口或类(类或抽象类)*分类实现*如多种对象类A或B*使用继承实现B类多样代处理和自己的处理**调用*定义A类定义B类*把A类代入B类调用B类为A装饰*再把装... 查看详情

15装饰器开闭原则代参装饰器多个装饰器同一函数应用(代码片段)

今日主要内容关于函数的装饰器.1.装饰器(重点,难点)开闭原则:对功能的扩展开放对代码的修改是封闭通用装饰器语法:defwrapper(fn):definner(*args,**kwargs):#聚合在目标函数之前ret=fn(*arg,**kwargs)#打散在目标函数之后returnretreturninner@wrapper... 查看详情

吾尝终日而思矣——2019.02.19

...一个加强后的子类。引用:http://www.runoob.com/design-pattern/decorator-pattern.html 3.java大数据处理调优引用:https://www.cnblogs.com/law-luffy/p/6061183.html 4.IO与NIONIO主要有三大核心部分:Channel(通道),Buffer(缓冲区),Selector。传统IO基于字... 查看详情

设计模式——十七:装饰器模式(代码片段)

@目录什么是装饰器模式?为什么要用装饰器模式使用装饰器模式前引入装饰器模式装饰器模式优缺点装饰器模式优点装饰器模式缺点装饰器模式应用场景扩展-Java中的装饰器模式什么是装饰器模式?装饰器模式的定义:Attachadditi... 查看详情

装饰器模式

7种结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。装饰器模式:动态的给一个对象添加额外的功能,被装饰对象和装饰对象必须实现同一个接口,装饰对象持有被装饰对象的实... 查看详情

设计模式之装饰模式

什么是装饰模式装饰(Decorator)模式又叫做包装模式。通过一种对client透明的方式来扩展对象的功能,是继承关系的一个替换方案。装饰模式的结构装饰模式的角色和职责抽象组件角色:一个抽象接口,是被装饰类和装饰类的父... 查看详情

设计模式---装饰者模式(代码片段)

装饰者模式介绍角色示例代码星巴克咖啡的例子方案一方案二:将调料内置到Drink类中方案三:装饰者模式代码演示装饰者模式的简化透明性的要求半透明的装饰模式装饰模式的优点装饰模式的缺点装饰模式注意事项适用场景... 查看详情

戏说模式之装饰者模式(游戏)

装饰者模式定义装饰者(Decorator)模式提供了一种方法,使得在不改变原有类的基础上可以动态的扩展一个对象的功能。即动态的将指着附加到对象上。装饰者模式的特点1、装饰对象和被装饰对象实现了相同的接口。客户端可以不... 查看详情

javaio装饰者模式

 装饰模式(Decorator)  装饰模式又名包装(Wrapper)模式。  装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。  装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。  ... 查看详情

设计模式之装饰者模式

一.装饰者模式特点:1.装饰者和被装饰者对象有相同的超类2.可以用一个或多个装饰者包装一个对象3.由于装饰者和被装饰者具有相同超类,所以任何需要被包装对象的场合,可以用装饰过的对象代替4.装饰者可以再所委托被装饰... 查看详情

设计模式——装饰器模式(decoratorpattern)

装饰器模式(DecoratorPattern)概念装饰器模式 允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰者可以在所委托被装饰者的行为之前或之后加上自己的行为,以达到特定的目的。组成装饰器模式由组件和装饰... 查看详情

装饰模式

装饰模式:动态的给一个对象添加一些额外的职责,就增加功能呢来说,装饰模式比生成自来更为灵活。 装饰模式与职责链模式的差别:1,装饰模式,增加这些功能会被一一执行。2,职责链模式:并不是职责链中所有的职... 查看详情

decorator装饰模式(代码片段)

装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰模式的结构装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象... 查看详情

装饰者模式

装饰模式介绍装饰模式是结构型设计模式之一,不使用继承和改变类文件的情况下,动态地扩展一个对象的功能,是继承的替代方案之一(就增加功能来说,装饰者模式相比生成子类更为灵活。)。它是通过创建一个包装对象,... 查看详情

23种设计模式——装饰模式单一职责

文章目录意图什么时候使用装饰真实世界类比装饰模式的实现装饰模式的优缺点亦称:装饰者模式、装饰器模式、Wrapper、Decorator意图装饰者模式(DecoratorPattern)允许向一个现有的对象扩展新的功能,同时不改变其结构。主要解... 查看详情

装饰模式

装饰模式简介所谓的装饰模式,就是为已有的功能动态地添加更多功能的一种方式。跟策略模式类似,装饰模式中新添加的功能同样具有“可插拔性”。不同的是,在装饰模式中,可以同时添加不止一个新功能。在装饰模式中,... 查看详情

设计模式装饰模式

装饰模式装饰模式,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活.装饰模式就是为了给已有功能动态添加更多功能。它解决的主要问题就是:之前的代码如果要给ConcretePerson加功能,就... 查看详情