C++抽象类实现调用被装饰函数及类装饰器调用疑问
嘿,针对你提出的两个C++装饰器相关问题,我来一步步帮你梳理解决方案和思路:
问题1:在抽象类的具体实现中如何内部调用被装饰函数
首先先给你提个小细节:你的NPCDecorator类里的get()方法漏了返回值,这会导致编译错误,应该改成:
int get() { return npc->get(); }
好了,回到核心问题——装饰器内部怎么调用被装饰对象的原始函数?其实很简单:装饰器类持有被装饰对象的指针(也就是你代码里的npc),直接通过这个指针调用对应的方法就行。
举个例子,如果Elite想要在自己的get()里先调用原始NPC的get()再做扩展,而不是直接返回100,就可以这么写:
int get() { // 调用被装饰对象的原始get() int base_val = npc->get(); // 基于原始值扩展 return base_val + 90; }
当然,你也可以通过调用基类NPCDecorator的get()来转发,效果是一样的:int base_val = NPCDecorator::get();。本质都是通过持有的被装饰对象来访问它的方法。
问题2:让NPC::calc()调用Elite::get()而非自身的get(),及最优方案探讨
问题根源
现在你的NPC::calc()里的get()调用,本质是this->get()——也就是调用当前NPC实例自己的get()方法。而Elite是一个包裹了NPC的装饰器对象,两者是完全独立的实例,所以默认情况下calc()根本不知道Elite的存在。要解决这个问题,我们有几种不同的思路:
方案1:用虚函数让装饰器接管calc()逻辑(推荐)
这是最符合装饰器模式设计思想的方案:把calc()放到抽象基类AbstractNPC中并设为虚函数,然后让NPCDecorator实现这个方法,并且在装饰器的calc()里调用自己的get()(也就是被装饰后的版本),而不是被装饰对象的get()。
修改后的完整代码如下:
#include <iostream> using namespace std; class AbstractNPC { public: virtual int get() = 0; // 把calc()升级为抽象虚函数,让所有子类都实现 virtual void calc() = 0; }; class NPC: public AbstractNPC { public: NPC() { } int get(){ return 10; } void calc(){ // 这里的get()会被动态绑定到实际对象的版本 int i = get(); cout << "NPC calc got value: " << i << endl; } }; class NPCDecorator: public AbstractNPC { private: AbstractNPC * npc; public: NPCDecorator(AbstractNPC *n) : npc(n) { } int get() { return npc->get(); } void calc() { // 这里调用的是装饰器自身的get(),也就是子类(比如Elite)的版本 int i = get(); cout << "Decorated calc got value: " << i << endl; // 如果需要复用NPC的calc逻辑,可以把结果传过去,或者调整逻辑 // npc->process_calc_result(i); } }; class Elite: public NPCDecorator { public: Elite(AbstractNPC *n): NPCDecorator(n) { } int get() { return 100; } }; // 使用示例 int main() { AbstractNPC* base_npc = new NPC(); AbstractNPC* elite_npc = new Elite(base_npc); elite_npc->calc(); // 这里会调用Elite::get(),输出100 delete elite_npc; delete base_npc; return 0; }
这个方案的优点很明显:
- 完全符合开闭原则,不需要修改原有
NPC类的核心逻辑,只通过扩展装饰器来实现功能 - 所有操作都通过
AbstractNPC接口进行,代码耦合度低,后续加新的装饰器(比如Boss、Rare)都很方便 - 完美契合装饰器模式的设计初衷:通过包裹对象来透明地扩展功能
方案2:用函数指针/std::function动态指定get()的调用目标
如果不想修改抽象基类的结构,这个方案可以快速实现需求,但耦合度会高一些。我们给NPC类加一个可动态设置的函数成员,让calc()优先调用这个函数,而不是自身的get():
修改后的NPC类和Elite类:
#include <iostream> #include <functional> using namespace std; class AbstractNPC { public: virtual int get() = 0; }; class NPC: public AbstractNPC { public: // 用std::function代替原始函数指针,更灵活支持捕获lambda std::function<int()> custom_get; NPC() : custom_get(nullptr) { } int get(){ return 10; } void calc(){ // 优先调用自定义的get(),否则用自身的 int i = (custom_get) ? custom_get() : get(); cout << "NPC calc got value: " << i << endl; } }; class NPCDecorator: public AbstractNPC { private: AbstractNPC * npc; public: NPCDecorator(AbstractNPC *n) : npc(n) { } int get() { return npc->get(); } }; class Elite: public NPCDecorator { private: NPC* target_npc; public: Elite(AbstractNPC *n): NPCDecorator(n) { // 把抽象指针转换为具体的NPC指针(需要确保传入的是NPC实例) target_npc = dynamic_cast<NPC*>(n); if (target_npc) { // 用lambda捕获当前Elite实例的this,绑定到custom_get target_npc->custom_get = [this]() { return this->get(); }; } } int get() { return 100; } };
使用的时候,调用elite_npc包裹的NPC的calc(),就会得到100了。但这个方案有几个缺点:
Elite需要知道NPC的内部结构(custom_get成员),耦合度高- 如果传入
NPCDecorator的不是NPC实例,dynamic_cast会失败,需要额外的错误处理 - 扩展性差,后续加新的装饰器都要重复类似的绑定逻辑
最优方案总结
如果追求代码的可维护性和扩展性,**方案1(虚函数+装饰器接管calc())**是绝对的首选,它完全遵循设计模式的原则,代码结构清晰,后续扩展成本低。而函数指针/std::function的方案只适合一些临时的、小规模的修改场景,不适合长期维护的代码。
内容的提问来源于stack exchange,提问作者Findus

