我有一个带有纯虚方法的类功能。
class Feature {
public:
virtual ~Feature() {}
virtual const float getValue(const vector<int>& v) const = 0;
};
此类由多个类实现,例如FeatureA和FeatureB。 单独的类Computer(简化)使用getValue方法进行一些计算。
class Computer {
public:
const float compute(const vector<Feature*>& features, const vector<int>& v) {
float res = 0;
for (int i = 0; i < features.size(); ++i) {
res += features[i]->getValue(v);
}
return res;
}
};
现在,我想实现FeatureC,但我意识到我需要getValue方法中的其他信息。 FeatureC中的方法类似于
const float getValue(const vector<int>& v, const vector<int>& additionalInfo) const;
我当然可以在Feature,FeatureA,FeatureB中修改getValue的签名,以将additionalInfo作为参数,并在compute方法中添加additionalInfo作为参数。但是如果我想要实现需要更多附加信息的FeatureD,我可能必须稍后再次修改所有这些签名。我想知道是否有一个更优雅的解决方案,或者是否有一个已知的设计模式,你可以指出我进一步阅读。
答案 0 :(得分:2)
您至少有两个选择:
getValue()
,而是传递结构。在这个结构中,您可以在今天放置向量,明天可以放入更多数据。当然,如果程序的某些具体运行不需要额外的字段,那么计算它们的需要可能是浪费的。但是,如果你总是需要计算所有数据(即如果总会有一个FeatureC),它将不会造成性能损失。getValue()
对具有获取必要数据的方法的对象的引用。该对象可以是计算机本身,也可以是一些更简单的代理。然后getValue()
实现可以准确地请求他们需要的东西,并且它可以被懒惰地计算。在某些情况下,懒惰会消除浪费的计算,但是这样做的总体结构会因为必须调用(可能是虚拟的)函数来获取各种数据而产生一些小的不变开销。答案 1 :(得分:1)
要求Feature类层次结构的用户根据类调用不同的方法会失败多态。一旦你开始做dynamic_cast<>()
,你就知道你应该重新考虑你的设计。
如果子类需要只能从其调用者获取的信息,则应更改getValue()方法以获取additionalInfo参数,并在无关紧要的类中忽略该信息。
如果FeatureC可以通过调用另一个类或函数来获取additionalInfo,那通常是一种更好的方法,因为它限制了需要了解它的类的数量。也许数据可以从FeatureC通过其构造函数或单个对象访问的对象获得,或者可以通过调用函数来计算。找到最佳方法需要更多关于案例的知识。
答案 2 :(得分:1)
这个问题在C ++编码标准(Sutter,Alexandrescu)的第39项中得到解决,标题为“考虑使虚拟函数非公共,公共函数是非虚拟的。”
特别是,遵循非虚拟接口设计模式的动机之一(这是项目的全部内容)被声明为
每个界面都可以采用其自然形状:当我们分开公共界面时 从定制界面,每个人都可以轻松地自然地采取形式 我想采取措施,而不是试图寻找迫使他们看起来的妥协 相同。通常,这两个接口需要不同数量的功能和/或 不同参数; [...]
这特别有用
在具有高成本变化的基类中
另一种在这种情况下非常有用的设计模式是访客模式。至于基类(以及整个层次结构)具有高成本变化时适用的NVI。您可以找到关于此设计模式的大量讨论,我建议您阅读Modern C ++(Alexandrescu)中的相关章节,(在旁边)让您对如何使用(非常易于使用)访客设施有很好的了解在loki
我建议您阅读所有这些材料,然后编辑问题,以便我们为您提供更好的答案。我们可以提出各种解决方案(例如,如果需要,可以使用额外的方法为类提供额外的参数),这可能不适合您的情况。
尝试解决以下问题:
现在我们有了一些“理论”,让我们的目标是使用访客模式的实践:
#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;
}
您可以尝试使用此代码here。 这是访客模式的粗略实现,正如您所看到的,它可以解决您的问题。我强烈建议你不要试图以这种方式实现它,有明显的依赖性问题可以通过称为非循环访问者的改进来解决。它已在Loki中实现,因此无需担心实现它。
除了实现之外,您可以看到您不依赖于类型开关(正如其他人指出的那样,您应该尽可能避免)并且您不要求类具有任何特定接口(例如,一个参数用于计算功能)。此外,如果访问者类是层次结构(使计算机成为示例中的基类),则当您要添加此类功能时,不需要向层次结构添加任何新功能。
如果您不喜欢visitA,请访问B,...“模式”,请不要担心:这只是一个简单的实现,您不需要它。基本上,在实际实现中,您使用访问函数的模板特化。
希望这有所帮助,我付出了很多努力:)
答案 3 :(得分:0)
要正常工作的虚函数需要具有完全相同的“签名”(相同的参数和相同的返回类型)。否则,你只是得到一个“新成员函数”,这不是你想要的。
这里真正的问题是“调用代码如何知道它需要额外的信息”。
您可以通过几种不同的方式解决这个问题 - 第一种方法是始终传入const vector <int>& additionalInfo
,无论是否需要。
如果那是不可能的,因为除了additionalInfo
的情况之外没有任何FeatureC
,你可以有一个“可选”参数 - 这意味着使用一个指向vector的指针({{ 1}}),当值不可用时为NULL。
当然,如果vector<int>* additionalInfo
是一个可以存储在FeatureC类中的值,那么这也可以。
另一种选择是扩展基类additionalInfo
以增加两个选项:
Feature
然后让你的循环像这样:
class Feature {
public:
virtual ~Feature() {}
virtual const float getValue(const vector<int>& v) const = 0;
virtual const float getValue(const vector<int>& v, const vector<int>& additionalInfo) { return -1.0; };
virtual bool useAdditionalInfo() { return false; }
};