【读书笔记】《C++ Software Design》第九章:The Decorator Design Pattern

发布于:2025-07-14 ⋅ 阅读:(13) ⋅ 点赞:(0)

《C++ Software Design》第九章:The Decorator Design Pattern

在现实系统中,我们常常需要在不修改已有类的前提下,为对象添加新功能——比如增加日志记录、性能度量、缓存行为等等。这种按需叠加式功能扩展是软件工程中典型的需求。

Decorator(装饰器)模式正是为此而生的一种经典结构型设计模式。它提供了一种透明、组合式的方式来增强对象的行为,是继承替代方案中最灵活的一种。


Guideline 35:使用装饰器层次化地扩展功能

可能遇到的问题

假设你负责一个 DataSource 类,有人需要在读取数据时加密,另一个同事希望加上缓存,还有人需要日志跟踪。传统继承方式无法灵活组合这些需求:

class EncryptedDataSource : public DataSource {};
class CachedDataSource : public DataSource {};
class LoggingDataSource : public DataSource {};

那如果有人想要“加密+缓存+日志”呢?就不得不写出一系列组合子类:EncryptedCachedDataSourceWithLogging ……代码膨胀不可避免。


Decorator 模式解释

Decorator 模式通过组合而不是继承,将增强行为附加到已有对象上,并允许以层级结构形式叠加功能。

核心结构:
class DataSource {
public:
    virtual std::string read() = 0;
    virtual ~DataSource() = default;
};

class FileDataSource : public DataSource {
public:
    std::string read() override {
        return "file_data";
    }
};

class DataSourceDecorator : public DataSource {
protected:
    std::shared_ptr<DataSource> wrappee;
public:
    DataSourceDecorator(std::shared_ptr<DataSource> src) : wrappee(std::move(src)) {}
};

class LoggingDecorator : public DataSourceDecorator {
public:
    using DataSourceDecorator::DataSourceDecorator;

    std::string read() override {
        std::cout << "[LOG] Reading data\n";
        return wrappee->read();
    }
};

class CachingDecorator : public DataSourceDecorator {
    std::string cachedData;
public:
    using DataSourceDecorator::DataSourceDecorator;

    std::string read() override {
        if (cachedData.empty()) {
            cachedData = wrappee->read();
        }
        return cachedData;
    }
};

使用方式:

auto src = std::make_shared<FileDataSource>();
auto cached = std::make_shared<CachingDecorator>(src);
auto logged = std::make_shared<LoggingDecorator>(cached);

std::string data = logged->read();

你可以任意组合装饰器而不改变底层类。


第二个例子:装饰图形对象

class Shape {
public:
    virtual void draw() const = 0;
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Circle\n";
    }
};

class ColorDecorator : public Shape {
    std::shared_ptr<Shape> shape;
    std::string color;
public:
    ColorDecorator(std::shared_ptr<Shape> s, std::string c)
        : shape(std::move(s)), color(std::move(c)) {}

    void draw() const override {
        shape->draw();
        std::cout << "With color: " << color << "\n";
    }
};

Decorator vs Adapter vs Strategy

模式 作用 是否替代接口 是否组合行为 使用粒度
Decorator 添加可选功能 实例
Adapter 转换接口 类/接口
Strategy 替换算法

Decorator 最大的特点是“功能增强叠加而非行为切换”。


Decorator 的短板分析

  • 对于每个功能点都需一个子类,复杂度线性增长
  • 不适合替换核心行为,只适合“附加行为”
  • 调试时可能形成长链,调试栈难以定位
  • 默认实现为运行时结构,可能存在虚函数调用开销

Guideline 36:理解运行时与编译时抽象的权衡

C++ 作为静态类型语言,其类型系统可在编译期执行许多抽象操作。Decorator 模式既可在运行时组合(如上所示),也可借助模板系统在编译期静态组合,从而实现零开销的增强。


编译时 Decorator:值语义的组合

通过模板类嵌套实现组合行为:

template <typename Base>
class LoggingDecorator {
public:
    Base base;
    void run() {
        std::cout << "[LOG] Before\n";
        base.run();
        std::cout << "[LOG] After\n";
    }
};

class Task {
public:
    void run() {
        std::cout << "Running task\n";
    }
};

使用:

LoggingDecorator<Task> task;
task.run();

特点:

  • 零运行时开销(无虚函数)
  • 组合发生在编译期
  • 更易内联优化

运行时 Decorator:基于值语义封装

可用 std::function 或类型擦除实现值类型的 runtime decorator:

std::function<void()> f = [] {
    std::cout << "Real task\n";
};

std::function<void()> logWrapper = [f]() {
    std::cout << "[LOG] Start\n";
    f();
    std::cout << "[LOG] End\n";
};

logWrapper();

适用于任务调度、管线执行等异步场景。


对比分析

特性 编译时 Decorator 运行时 Decorator
性能 高(无虚调用) 中等(虚调用或类型擦除)
灵活性 差(必须编译期组合) 高(运行时可决定组合)
可维护性 中(模板膨胀) 高(抽象清晰)
类型侵入性 强(需暴露模板类型) 弱(可封装任意对象)

小结

Decorator 模式在 C++ 中拥有灵活实现方式:

  • 作为运行时功能扩展机制,它是继承结构的有效替代
  • 借助模板,可在编译期静态组合增强,获得更优性能
  • 适合场景包括日志增强、缓存包装、动态功能组合、绘图增强等

搭配第八章的类型擦除技术,Decorator 模式还可以实现 非侵入式、运行时组合的功能链条,在现代微服务、中间件框架、事件驱动系统中发挥重要作用。

在高质量 C++ 软件架构设计中,Decorator 模式帮助我们延迟功能绑定、支持渐进式增强,避免陷入硬编码的继承层级。同时,它也体现了“组合优于继承”的软件设计哲学。


网站公告

今日签到

点亮在社区的每一天
去签到