装饰器模式(Decorator Pattern)

C++ 装饰器模式(Decorator Pattern)


目录

  1. 模式概述与定义
  2. 核心解决痛点
  3. 四大核心角色详解
  4. 模式结构时序说明
  5. C++ 标准面向对象实现(咖啡经典案例)
  6. C++ 函数式轻量装饰器(Lambda/可调用对象)
  7. 装饰器嵌套组合规则
  8. 装饰器 VS 适配器 VS 代理模式深度对比
  9. 适用场景 & 不适用场景
  10. 优缺点详细拆解
  11. C++ 工程最佳实践
  12. 常见坑点与避坑
  13. 面试高频题及标准答案
  14. C++ 实际项目落地场景

1. 模式概述与定义

装饰器模式属于结构型设计模式

定义

动态地给一个对象 添加额外的功能,无需通过继承生成大量子类,也不修改原类源码。

它以包装组合 的方式,层层装饰原有对象,实现功能叠加

核心设计思想

  • 组合替代继承
  • 接口保持一致,对外透明
  • 动态扩展、任意组合功能
  • 遵循开闭原则、单一职责

生活化类比

裸奶茶 → 加珍珠 → 加奶盖 → 加冰;

基础对象不变,配料层层装饰,任意搭配。


2. 核心解决痛点

  1. 继承泛滥导致类爆炸
    每新增一个功能就要新增一个子类,功能组合一多,子类数量指数级增长。
  2. 功能需要动态按需添加
    运行时才决定要不要加日志、缓存、权限、重试。
  3. 原有业务类禁止修改
    核心代码稳定,不能侵入改动,只能外部扩展。
  4. 功能可自由排列组合
    不同业务需要不同功能组合,硬编码无法覆盖。
  5. 横向扩展能力
    统一接口下,新增装饰器无需改动原有代码。

3. 四大核心角色详解

角色 英文 职责说明
抽象组件 Component 定义统一接口,原始对象和所有装饰器都实现该接口
具体组件 ConcreteComponent 原始核心对象,实现基础业务功能,被装饰的本体
抽象装饰器 Decorator 继承抽象组件,内部持有抽象组件成员,作为所有装饰器父类
具体装饰器 ConcreteDecorator 在原有功能前后添加额外逻辑,实现功能增强

核心约束:装饰器和被装饰对象必须实现同一个接口,客户端完全无感知。


4. 模式调用结构说明

客户端 → 抽象组件接口

→ 具体装饰器(外层)

→ 内部持有下一层组件

→ 最终调用到具体组件真实逻辑

特点:层层包裹、递归调用、接口透明


5. C++ 标准面向对象实现(经典咖啡案例)

5.1 抽象组件

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>

// 抽象组件:饮品统一接口
class Beverage
{
public:
    virtual ~Beverage() = default;
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
};

5.2 具体组件(基础本体)

cpp 复制代码
// 具体组件:浓缩咖啡
class Espresso : public Beverage
{
public:
    std::string getDescription() const override
    {
        return "浓缩咖啡";
    }
    double getCost() const override
    {
        return 12.0;
    }
};

// 具体组件:美式咖啡
class Americano : public Beverage
{
public:
    std::string getDescription() const override
    {
        return "美式咖啡";
    }
    double getCost() const override
    {
        return 10.0;
    }
};

5.3 抽象装饰器

cpp 复制代码
// 抽象装饰器
class CondimentDecorator : public Beverage
{
protected:
    std::unique_ptr<Beverage> beverage;
public:
    explicit CondimentDecorator(std::unique_ptr<Beverage> bev)
        : beverage(std::move(bev)) {}
};

5.4 具体装饰器(扩展功能)

cpp 复制代码
// 牛奶装饰器
class Milk : public CondimentDecorator
{
public:
    using CondimentDecorator::CondimentDecorator;

    std::string getDescription() const override
    {
        return beverage->getDescription() + " + 牛奶";
    }
    double getCost() const override
    {
        return beverage->getCost() + 3.5;
    }
};

// 方糖装饰器
class Sugar : public CondimentDecorator
{
public:
    using CondimentDecorator::CondimentDecorator;

    std::string getDescription() const override
    {
        return beverage->getDescription() + " + 方糖";
    }
    double getCost() const override
    {
        return beverage->getCost() + 2.0;
    }
};

// 焦糖装饰器
class Caramel : public CondimentDecorator
{
public:
    using CondimentDecorator::CondimentDecorator;

    std::string getDescription() const override
    {
        return beverage->getDescription() + " + 焦糖";
    }
    double getCost() const override
    {
        return beverage->getCost() + 4.0;
    }
};

5.5 客户端使用 & 多层嵌套装饰

cpp 复制代码
int main()
{
    // 基础咖啡
    auto bev = std::make_unique<Espresso>();
    std::cout << bev->getDescription() << " 价格:" << bev->getCost() << "\n";

    // 一层装饰:加牛奶
    auto withMilk = std::make_unique<Milk>(std::move(bev));

    // 二层装饰:再加方糖
    auto withSugar = std::make_unique<Sugar>(std::move(withMilk));

    // 三层装饰:再加焦糖
    auto full = std::make_unique<Caramel>(std::move(withSugar));

    std::cout << full->getDescription() << " 价格:" << full->getCost() << "\n";

    return 0;
}

6. C++ 轻量函数式装饰器(业务常用)

无需定义类继承,用包装函数实现方法装饰,适合日志、耗时、拦截器。

cpp 复制代码
#include <functional>
#include <ctime>

// 原始业务函数
void businessFunc(int val)
{
    std::cout << "业务执行:" << val << "\n";
}

// 日志装饰器
template<typename Func>
void logDecorator(Func&& f, int val)
{
    std::cout << "[日志] 方法开始执行\n";
    f(val);
    std::cout << "[日志] 方法执行结束\n";
}

// 耗时装饰器
template<typename Func>
void timeDecorator(Func&& f, int val)
{
    auto start = clock();
    f(val);
    auto cost = clock() - start;
    std::cout << "[耗时] " << cost << " 时钟周期\n";
}

// 嵌套使用
int main()
{
    logDecorator([](int v){
        timeDecorator(businessFunc, v);
    }, 100);
    return 0;
}

7. 装饰器嵌套组合规则

  1. 接口一致性:所有装饰器必须和原始对象同接口;
  2. 顺序可调整:装饰先后顺序不同,执行逻辑前后缀不同;
  3. 无层数限制 :可无限嵌套,工程建议控制在 3层以内
  4. 装饰器可复用:同一个装饰器可装饰任意不同具体组件。

8. 装饰器 VS 适配器 VS 代理 深度对比

模式 类型 核心目的 接口变化 典型场景
装饰器 结构型 动态增强功能 接口不变 日志、缓存、加配料、中间件
适配器 结构型 接口转换兼容 接口改变 旧系统适配、第三方SDK适配
代理模式 结构型 控制访问/隔离 接口不变 远程代理、懒加载、权限控制

一句话区分:

  • 加功能用装饰器
  • 转接口用适配器
  • 做控制/隔离用代理

9. 适用场景 & 不适用场景

适用场景

  1. 需要动态给对象附加功能,且功能可任意组合;
  2. 继承会造成大量子类爆炸;
  3. 核心业务类稳定,禁止修改源码;
  4. 中间件链路、请求拦截、日志、耗时、权限、重试包装;
  5. 流处理、IO包装、分层过滤系统;
  6. 商品/饮品/配置类的可选属性叠加。

不适用场景

  1. 功能固定、无需动态组合;
  2. 接口差异很大,需要做适配而非增强;
  3. 简单对象,没必要做多层包装。

10. 优缺点详细拆解

优点

  1. 遵循开闭原则:新增装饰器不修改原有代码;
  2. 组合优于继承:彻底解决子类爆炸;
  3. 动态灵活:运行时按需添加、移除功能;
  4. 单一职责:每个装饰器只负责一个增强逻辑;
  5. 透明无感:客户端无需感知是否被装饰。

缺点

  1. 多层嵌套后调用链路复杂,调试难度上升;
  2. 会产生大量小粒度装饰器类;
  3. 装饰顺序会影响最终执行结果,需要业务把控;
  4. 过度嵌套会带来轻微调用开销。

11. C++ 工程最佳实践

  1. 统一使用 std::unique_ptr 管理组件,避免裸指针内存泄漏;
  2. 装饰器只做前置/后置增强,不改写核心业务逻辑;
  3. 严格遵守同接口约束,保证透明替换;
  4. 控制装饰嵌套层数,建议不超过3层;
  5. 固定常用组合可配合工厂模式封装创建过程;
  6. 通用横向能力(日志、监控、耗时)优先用函数式装饰器

12. 常见坑点与避坑

  1. :装饰器不继承同一接口,客户端无法统一调用
    避坑:强制所有装饰器继承顶层抽象组件。
  2. :装饰器内部裸指针管理不当造成内存泄漏
    避坑:全程使用智能指针转移所有权。
  3. :装饰器中修改原有核心业务逻辑
    避坑:装饰只做增强,不侵入本体逻辑。
  4. 无序堆叠装饰器,业务逻辑混乱
    避坑:约定装饰顺序,通用前置拦截放外层。

13. 面试高频题及标准答案

Q1:装饰器模式核心作用?

在不修改原类、不依赖多层继承的前提下,动态给对象叠加额外功能,通过组合包装实现灵活扩展。

Q2:装饰器为什么比继承好?

继承是静态绑定、类爆炸、耦合高;装饰器是动态组合、接口透明、可任意搭配、符合开闭原则。

Q3:装饰器和适配器的区别?

装饰器接口不变,重在功能增强 ;适配器接口转换,重在兼容适配

Q4:装饰器和代理模式区别?

装饰器侧重功能叠加增强 ;代理侧重访问控制、隔离、懒加载、远程调用

Q5:C++ 哪些场景适合用装饰器?

日志拦截、耗时统计、权限校验、缓存包装、IO流分层、业务功能动态组合。


14. C++ 实际项目落地场景

  1. 网络中间件:请求加日志、加签名、加限流、加重试;
  2. IO流处理:文件流、缓冲流、加密流层层包装;
  3. 业务服务包装:通用横切逻辑统一装饰;
  4. 配置/实体类:可选属性动态叠加;
  5. 游戏角色:装备、Buff 动态加成;
  6. 接口框架:拦截器、过滤器链路实现。

相关推荐
Yolo566Q1 小时前
环境土壤物理模型HYDRUS1D/2D/3D实践技术应用系统性学习
大数据·开发语言·gpt·学习·arcgis·r语言
Alson_Code1 小时前
Spring Ai Alibaba
java·人工智能·spring
计算机安禾1 小时前
【c++面向对象编程】第5篇:类与对象(四):赋值运算符重载
java·前端·c++
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第45题】【JVM篇】第5题:JVM中,对象何时会进入老年代?
java·开发语言·jvm·后端·面试
hanbr1 小时前
C++ 类型转换与异常处理全解析
开发语言·c++
luck_bor1 小时前
使用接口定义规范,实现类完成具体逻辑
java·开发语言
Tsuki_tl1 小时前
Thread类的基本用法干货总结
java·javase·线程中断·休眠·线程等待·thread类
小小de风呀1 小时前
de风——【从零开始学C++】(六):模板初阶
开发语言·c++
likerhood1 小时前
java的泛型(generics)详细讲解
java·开发语言