您的位置:首页 >C++实现简单的装饰器模式 _ 动态添加对象功能职责【源码】
发布于2026-04-21 阅读(0)
扫一扫,手机访问
C++装饰器模式通过继承同一抽象接口并组合被装饰对象实现行为叠加,不改变原接口签名;核心是装饰器持接口指针并在前后插入逻辑,需用智能指针管理嵌套生命周期,虚函数方案兼顾动态组合与维护性。

@log很多从 Python 转过来的朋友,可能都怀念那个简洁的 @log 语法糖。但到了 C++ 这儿,情况就不同了。C++ 语言本身并没有提供运行时注解或者语法级别的装饰器支持。所以,我们常说的“C++ 装饰器模式”,其实是经典 GoF 结构型模式的一种手动实现——它依靠“继承”加“组合”这两大法宝,在编译期就构建好职责链。这种模式的核心优势在于,它绝不改变原有对象的类型签名,却能以一种近乎透明的方式,为对象叠加新的行为,比如添加日志记录、性能计时或者权限检查。关键在于,它追求的不是“看起来像装饰”,而是实打实的“调用接口保持不变,行为却能灵活叠加”。
想要实现这种可叠加的装饰效果,关键在于理清几个角色之间的关系。整个架构的核心,是让装饰器类和被装饰的类,都去实现同一个抽象接口。装饰器内部则持有一个指向该接口的指针(或者引用)。这样一来,所有对接口的调用,最终都会转发给那个被包裹的对象,而装饰器要做的,就是在转发前后,插入自己的那部分逻辑。
Component:这是整个体系的基石,一个纯虚基类,定义了统一的接口(比如一个 operation() 方法)。ConcreteComponent:这是原始功能的实现者,也就是我们需要被“装饰”的那个核心对象。Decorator:作为抽象装饰器,它同样继承自 Component。它的特殊之处在于,内部持有一个 Component* 类型的成员,用来链接被装饰对象。LoggingDecorator、TimingDecorator):它们继承自 Decorator,并重写接口方法。在方法内部,先执行自己的逻辑(如打印开始日志),然后调用 component_->operation(),最后再执行后续逻辑(如打印结束日志)。来看一个简化的代码片段,感受一下这个结构:
class Component {
public:
virtual ~Component() = default;
virtual void operation() = 0;
};
class ConcreteComponent : public Component {
public:
void operation() override { std::cout << "do work\n"; }
};
class Decorator : public Component {
protected:
Component* component_;
public:
explicit Decorator(Component* c) : component_(c) {}
void operation() override { component_->operation(); }
};
class LoggingDecorator : public Decorator {
public:
explicit LoggingDecorator(Component* c) : Decorator(c) {}
void operation() override {
std::cout << "[LOG] start\n";
component_->operation();
std::cout << "[LOG] end\n";
}
};
当需要给一个对象叠加多层“Buff”时,装饰器就会形成一条链。这条链的构造顺序,必须严格遵循“从里到外”的原则:先创建最核心的 ConcreteComponent,然后用它来构造 LoggingDecorator,再用这个包含了日志功能的中间对象,去构造外层的 TimingDecorator。顺序一旦搞反,很容易导致悬空指针或者对象被提前释放的灾难性后果。
立即学习“C++免费学习笔记(深入)”;
std::unique_ptr 来管理对象生命周期,能有效避免裸指针带来的管理混乱。std::shared_ptr 并形成循环引用(比如,装饰器又把自身传给了被装饰对象)。Decorator 的析构函数通常不应该去 delete 它所持有的 component_。这个生命周期的管理职责,应该交给最外层的装饰器,或者由对象的创建者来统一负责。当然,最省心的办法还是全程交给智能指针。来看一个相对安全的写法示例:
auto comp = std::make_unique(); auto logged = std::make_unique (comp.get()); auto timed = std::make_unique (logged.get()); // 注意:此时 comp 和 logged 必须比 timed 活得久
不过,更稳妥、所有权更清晰的写法,是全部使用 std::unique_ptr 并通过移动语义来构造:
auto comp = std::make_unique(); auto logged = std::make_unique (std::move(comp)); auto timed = std::make_unique (std::move(logged));
看到虚函数调用,有些追求极致性能的开发者可能会想:能不能用模板或者 CRTP(奇异递归模板模式)来实现,彻底消除运行时开销?
想法很好,但代价不小。模板版本的装饰器(比如 template)确实能避免虚函数调用的开销,但它也彻底破坏了“统一接口”和“运行时动态叠加”这两个核心能力。你无法将 LoggingDecorator 和 TimingDecorator 当作同一种类型存进容器,也无法在程序运行的时候,再决定到底要给一个对象叠加几层装饰。
Logging> 这种嵌套形式),而且装饰逻辑会与被装饰的具体类型紧密耦合,难以解耦和复用。最后,必须提醒一点:装饰器模式并非万能胶。它非常擅长增强“已有接口的行为”,但如果想给对象添加一个全新的接口(比如,突然想给 Component 加一个 sa ve_to_file() 方法),那装饰器就无能为力了,这时就得修改基类,或者考虑其他设计模式。另外,如果装饰层叠得太深,调试也会变得困难——错误调用栈里会塞满一连串的 Decorator::operation,想一眼看出问题到底出在哪一层,可不是件容易的事。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9