当前位置: 首页 > article >正文

c++常见设计模式之装饰器模式

基础介绍

装饰器模式是结构型设计模式,从字面意思看装饰器设计模式就是用来解决在原有的实现基础上添加一些额外的实现的问题。那么正统的概念是什么呢?装饰器模式允许我们动态的向对象添加新的 行为,同时不改变其原有的结构。它是一种比继承更灵活的扩展对象功能的方式。

举个简单的例子,比如手机作为一个产品,希望在基础手机的基础上实现新增两个功能1,且不希望改变类原有的结构,这种情况下就需要使用到装饰器模式。

实现原理

装饰器模式的实现原理:继承+虚函数的实现方式。典型的结构如下:

//基础的组件接口
class Component
{
    public:
    virtual std::string operation() = 0;  //纯虚函数,作为接口使用
}

//具体组件
class concreteComponent:public Component
{
    public:
    concreteComponent(Component* component):Component(component){}

    std::string operation()
    {
        return "concreteComponent";
    }
}

//装饰器基类
class Decorator: public Component
{
    public:
    Component* component_;  //该成员必须存在

    std::string operation() const override
    {
        return component_->operation();
    }
}
//具体的装饰器类A
class concreteDecoratorA: public Decorator
{
    public:
    concreteDecoratorA(Component* component):Decorator(component){}

    std::string operation() const override
    {
        reutrn "concreteDecoratorA " + Decorator::operation();
    }
}

//具体的装饰器类B
class concreteDecoratorB: public Decorator
{
    public:
    concreteDecoratorA(Component* component):Decorator(component){}

    std::string operation() const override
    {
        reutrn "concreteDecoratorB " + Decorator::operation();
    }
}

 原理解析:

  • 需要设计一个接口类,该接口定义了对象行为的基本接口,接口为纯虚函数
  • 需要一个基于该接口类的具体类,该具体类是一个具体的实现。
  • 需要设计一个装饰器基类,该基类同样的是一个接口类的一个具体实现。装饰器基类的具体功能是调用实际
  • 1个或者多个装饰器子类,在各个装饰器子类中实现了需要对原有接口进行动态添加行为的具体实现。

具体装饰器实例

下面的例子是一个具体的装饰器的例子:

#include <iostream>
#include <memory>
// 使用装饰器模式
class Coffee {
public:
    virtual double cost() = 0;
    virtual std::string description() = 0;
    virtual ~Coffee() = default;
};

class SimpleCoffee : public Coffee {
public:
    double cost() override { return 10.0; }
    std::string description() override { return "Simple Coffee"; }
};

// 装饰器基类
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee_;

public:
    CoffeeDecorator(std::unique_ptr<Coffee> coffee)
        : coffee_(std::move(coffee)) {}

    double cost() override { return coffee_->cost(); }
    std::string description() override { return coffee_->description(); }
};

// 具体装饰器
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> coffee)
        : CoffeeDecorator(std::move(coffee)) {}

    double cost() override {
        return CoffeeDecorator::cost() + 5.0;
    }

    std::string description() override {
        return CoffeeDecorator::description() + " with milk";
    }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> coffee)
        : CoffeeDecorator(std::move(coffee)) {}

    double cost() override {
        return CoffeeDecorator::cost() + 2.0;
    }

    std::string description() override {
        return CoffeeDecorator::description() + " with sugar";
    }
};

class ChocolateDecorator : public CoffeeDecorator {
public:
    ChocolateDecorator(std::unique_ptr<Coffee> coffee)
        : CoffeeDecorator(std::move(coffee)) {}

    double cost() override {
        return CoffeeDecorator::cost() + 3.0;
    }

    std::string description() override {
        return CoffeeDecorator::description() + " with chocolate";
    }
};

// 使用示例
int main() {
    // 创建一个简单的咖啡
    std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
    std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;

    // 加牛奶
    coffee = std::make_unique<MilkDecorator>(std::move(coffee));
    std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;

    // 加糖
    coffee = std::make_unique<SugarDecorator>(std::move(coffee));
    std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;

    // 再加巧克力
    coffee = std::make_unique<ChocolateDecorator>(std::move(coffee));
    std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;

    return 0;
}

代码运行解析

有了上面的代码,很多但是很多朋友对这个调用的过程不是很了解下面详细解析资源的转移、函数的调用:

  • 初始状态及加牛奶
// 初始创建
std::unique_ptr<Coffee> coffee = std::make_unique<SimpleCoffee>();
/* 内存状态:
coffee (unique_ptr) ---> SimpleCoffee对象
*/

// 输出初始状态
std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;
/* 函数调用过程:
1. coffee->description() 直接调用 SimpleCoffee::description()
2. coffee->cost() 直接调用 SimpleCoffee::cost()
*/

// 加牛奶
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
/* 执行过程:
1. std::move(coffee) 将原始coffee转为右值引用
2. MilkDecorator构造函数接收这个右值引用
3. 调用基类CoffeeDecorator构造函数
4. 原始coffee指针被存储在MilkDecorator的coffee_成员中
5. 原始coffee被置为nullptr
6. 新的unique_ptr指向MilkDecorator对象

最终内存状态:
coffee (unique_ptr) ---> MilkDecorator对象
                            |
                            +---> coffee_ (unique_ptr) ---> SimpleCoffee对象
*/
  • 加糖过程
// 加糖
coffee = std::make_unique<SugarDecorator>(std::move(coffee));
/* 执行过程:
1. std::move(coffee) 将指向MilkDecorator的coffee转为右值引用
2. SugarDecorator构造函数接收这个右值引用
3. 调用基类CoffeeDecorator构造函数
4. MilkDecorator对象的指针被存储在SugarDecorator的coffee_成员中
5. 原始coffee被置为nullptr
6. 新的unique_ptr指向SugarDecorator对象

最终内存状态:
coffee (unique_ptr) ---> SugarDecorator对象
                            |
                            +---> coffee_ (unique_ptr) ---> MilkDecorator对象
                                                               |
                                                               +---> coffee_ (unique_ptr) ---> SimpleCoffee对象
*/

// 输出当前状态
std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;
/* 函数调用过程:
description() 调用链:
1. SugarDecorator::description() 
   -> CoffeeDecorator::description() 
      -> MilkDecorator::description()
         -> CoffeeDecorator::description()
            -> SimpleCoffee::description()

cost() 调用链:
1. SugarDecorator::cost()
   -> CoffeeDecorator::cost()
      -> MilkDecorator::cost()
         -> CoffeeDecorator::cost()
            -> SimpleCoffee::cost()
*/
  • 加巧克力过程
// 加巧克力
coffee = std::make_unique<ChocolateDecorator>(std::move(coffee));
/* 执行过程:
1. std::move(coffee) 将指向SugarDecorator的coffee转为右值引用
2. ChocolateDecorator构造函数接收这个右值引用
3. 调用基类CoffeeDecorator构造函数
4. SugarDecorator对象的指针被存储在ChocolateDecorator的coffee_成员中
5. 原始coffee被置为nullptr
6. 新的unique_ptr指向ChocolateDecorator对象

最终内存状态:
coffee (unique_ptr) ---> ChocolateDecorator对象
                            |
                            +---> coffee_ (unique_ptr) ---> SugarDecorator对象
                                                               |
                                                               +---> coffee_ (unique_ptr) ---> MilkDecorator对象
                                                                                                  |
                                                                                                  +---> coffee_ (unique_ptr) ---> SimpleCoffee对象
*/

// 输出最终状态
std::cout << coffee->description() << ": $" << coffee->cost() << std::endl;
/* 函数调用链:
description() 调用链:
1. ChocolateDecorator::description()
   -> CoffeeDecorator::description()
      -> SugarDecorator::description()
         -> CoffeeDecorator::description()
            -> MilkDecorator::description()
               -> CoffeeDecorator::description()
                  -> SimpleCoffee::description()

cost() 调用链:
1. ChocolateDecorator::cost()
   -> CoffeeDecorator::cost()
      -> SugarDecorator::cost()
         -> CoffeeDecorator::cost()
            -> MilkDecorator::cost()
               -> CoffeeDecorator::cost()
                  -> SimpleCoffee::cost()
*/
  • 资源释放过程
// 当coffee离开作用域时,析构顺序:
1. 首先析构ChocolateDecorator
2. 然后析构SugarDecorator
3. 接着析构MilkDecorator
4. 最后析构SimpleCoffee

/* 析构过程是一个递归的过程:
1. ~ChocolateDecorator() 
   -> 析构其coffee_成员(SugarDecorator)
      -> ~SugarDecorator()
         -> 析构其coffee_成员(MilkDecorator)
            -> ~MilkDecorator()
               -> 析构其coffee_成员(SimpleCoffee)
                  -> ~SimpleCoffee()
*/


http://www.kler.cn/a/516914.html

相关文章:

  • 【技术洞察】2024科技绘卷:浪潮、突破、未来
  • composer安装指定php版本, 忽略平台原因导致的报错
  • No.36 学习 | Python 函数:从基础到实战
  • Linux进度条实现
  • 重构开源LLM分类:从二分到三分的转变
  • 常见的加密方式以及自定义加密工具
  • GPS信号生成:C/A码序列生成【MATLAB实现】
  • 基于单片机的水果保鲜仓库设计
  • OpenCV:高通滤波之索贝尔、沙尔和拉普拉斯
  • Day 16 卡玛笔记
  • opengrok_windows_多工程环境搭建
  • 小哆啦的编程冒险:罗马数字转整数
  • Golang笔记——静态强类型、编译型、并发型语言
  • Android OpenGL(六) 纹理
  • doris:Routine Load
  • ReUtil- 一个强大的正则表达式工具库
  • 【Linux】理解Linux中一切皆文件、缓冲区、ext2文件系统、软硬链接
  • build报错:Your build is currently configured to use incompatible Java 21.0.3 and Gradle 5.4.1Cannot...
  • HarmonyOS应用深浅适配
  • leetcode 123. 买卖股票的最佳时机 III
  • Grafana
  • 微信小程序中常见的 跳转方式 及其特点的表格总结(wx.navigateTo 适合需要返回上一页的场景)
  • 入门Stable-Diffusion-WebUI全过程
  • USART_串口通讯轮询案例(HAL库实现)
  • SQL-leetcode—1179. 重新格式化部门表
  • Kingbase数据库体系结构和日常运维监控