这个问题在 C++ 编码标准 (Sutter, Alexandrescu) 的第 39 项中得到解决,标题为“考虑将虚拟函数设为非公共的,将公共函数设为非虚拟的”。
特别是,遵循非虚拟接口设计模式(这就是项目的全部内容)的动机之一被表述为
每个界面都可以采用其自然形式:当我们将公共界面与定制界面分开时,每个界面都可以轻松地采用其自然想要采用的形式,而不是试图找到一种折衷方案,迫使它们看起来相同。通常,两个接口需要不同数量的功能和/或不同的参数;[...]
这个特别有用
在更改成本高的基类中
在这种情况下,另一个非常有用的设计模式是访问者模式。至于 NVI,它适用于基类(以及整个层次结构)具有高更改成本的情况。你可以找到很多关于这种设计模式的讨论,我建议你阅读 Modern C++ (Alexandrescu) 中的相关章节,它(侧面)让你对如何使用(非常容易使用)访问者设施有很好的了解在洛基
我建议您阅读所有这些材料,然后编辑问题,以便我们为您提供更好的答案。我们可以提出各种可能不适合您的情况的解决方案(例如,如果需要,使用为类提供附加参数的附加方法)。
尝试解决以下问题:
- 基于模板的解决方案是否适合该问题?
- 在调用函数时添加一个新的间接层是否可行?
- “推送参数”-“推送参数”-...-“推送参数”-“调用函数”方法会有帮助吗?(起初这可能看起来很奇怪,但想想类似“cout << arg << arg << arg << endl”,其中“endl”是“调用函数”)
- 您打算如何区分如何调用 Computer::compute 中的函数?
现在我们有了一些“理论”,让我们瞄准使用访客模式的实践:
#include <iostream>
using namespace std;
class FeatureA;
class FeatureB;
class Computer{
public:
int visitA(FeatureA& f);
int visitB(FeatureB& f);
};
class Feature {
public:
virtual ~Feature() {}
virtual int accept(Computer&) = 0;
};
class FeatureA{
public:
int accept(Computer& c){
return c.visitA(*this);
}
int compute(int a){
return a+1;
}
};
class FeatureB{
public:
int accept(Computer& c){
return c.visitB(*this);
}
int compute(int a, int b){
return a+b;
}
};
int Computer::visitA(FeatureA& f){
return f.compute(1);
}
int Computer::visitB(FeatureB& f){
return f.compute(1, 2);
}
int main()
{
FeatureA a;
FeatureB b;
Computer c;
cout << a.accept(c) << '\t' << b.accept(c) << endl;
}
您可以在此处尝试此代码。这是访问者模式的粗略实现,如您所见,它解决了您的问题。我强烈建议您不要尝试以这种方式实现它,存在明显的依赖问题,可以通过称为非循环访问者的改进来解决。它已经在 Loki 中实现,因此无需担心实现它。
除了实现之外,您可以看到您不依赖类型开关(正如其他人指出的那样,您应该尽可能避免)并且您不要求类具有任何特定接口(例如,计算函数的一个参数)。此外,如果访问者类是一个层次结构(在示例中将 Computer 设为基类),那么当您想要添加此类功能时,您不需要向该层次结构添加任何新功能。
如果您不喜欢 visitA、visitB、...“模式”,请不要担心:这只是一个简单的实现,您不需要它。基本上,在实际实现中,您使用访问函数的模板特化。
希望这会有所帮助,我已经付出了很多努力:)