几个类使用不同的api实现父类
several classes implement parent class with varying api
我有一个类Feature,它有一个纯虚拟方法。
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作为参数,也可以在计算方法中添加additionalInformation作为参数。但是,如果我想实现需要更多额外信息的FeatureD,我可能以后必须再次修改所有这些签名。我想知道是否有一个更优雅的解决方案,或者是否有一种已知的设计模式可以让我进一步阅读。
您至少有两个选项:
- 与其将单个向量传递给
getValue()
,不如传递一个结构。在这个结构中,您今天可以放置向量,明天可以放置更多数据。当然,如果程序的某些具体运行不需要额外的字段,那么计算这些字段的需要可能是浪费。但是,如果您总是需要计算所有数据(即,如果总是有一个FeatureC),它不会带来性能损失 - 将对具有获取必要数据的方法的对象的引用传递给
getValue()
。这个对象可以是计算机本身,也可以是一些更简单的代理。然后getValue()
实现可以准确地请求它们所需要的,并且可以延迟计算。在某些情况下,这种惰性将消除浪费的计算,但由于必须调用(可能是虚拟的)函数来获取各种数据,因此以这种方式进行计算的总体结构将施加一些较小的恒定开销
要求Feature类层次结构的用户根据类调用不同的方法会破坏多态性。一旦你开始做dynamic_cast<>()
,你就知道你应该重新思考你的设计。
如果子类需要只能从调用方获得的信息,则应更改getValue()方法以获取additionalInfo参数,并在无关紧要的类中忽略该信息。
如果FeatureC可以通过调用另一个类或函数来获得additionalInfo,那么这通常是一种更好的方法,因为它限制了需要了解它的类的数量。也许数据可以从FeatureC通过其构造函数访问的对象中获得,也可以从单例对象获得,或者可以通过调用函数来计算。找到最好的方法需要对案例有更多的了解。
这个问题在C++编码标准(Sutter,Alexandrescu)的第39项中得到了解决,该项题为"考虑使虚拟函数非公共,使公共函数非虚拟。"
特别是,遵循非虚拟接口设计模式的动机之一(这就是项目的全部内容)被称为
每个接口都可以呈现其自然形状:当我们将公共接口分离时从自定义界面,每个都可以很容易地采用它自然的形式想要接受,而不是试图找到一个迫使他们去寻找的妥协完全相同的通常,这两个接口需要不同数量的功能和/或不同参数;[…]
这是特别有用的
在具有高更改成本的基类中
在这种情况下,另一种非常有用的设计模式是Visitor模式。至于NVI,它适用于基类(以及整个层次结构)具有高更改成本的情况。你可以找到很多关于这个设计模式的讨论,我建议你阅读现代C++(Alexandrescu)中的相关章节,它(侧面)让你对如何使用loki 中的(非常容易使用)访客设施有了很好的了解
我建议你阅读所有这些材料,然后编辑这个问题,这样我们就能给你一个更好的答案。我们可以想出所有类型的解决方案(例如,如果需要,使用一个额外的方法为类提供额外的参数),这可能不适合您的情况。
尝试解决以下问题:
- 基于模板的解决方案适合这个问题吗
- 在调用函数时添加一个新的间接层是否可行
- 一个"推式论证"-"推式论据"--"push argument"-"call function"方法有帮助吗?(一开始这可能看起来很奇怪,但是想想类似于"cout<<arg<<arg<"的东西,其中"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;
}
你可以在这里试试这个代码。这是访问者模式的一个粗略实现,正如您所看到的,它解决了您的问题。我强烈建议您不要尝试以这种方式实现它,有一些明显的依赖性问题可以通过名为非循环访问者的改进来解决。它已经在洛基实施了,所以不需要担心实施它
除了实现之外,正如您所看到的,您不依赖类型开关(正如其他人所指出的,您应该尽可能避免),也不要求类具有任何特定的接口(例如,计算函数的一个参数)。此外,如果访问者类是一个层次结构(在本例中,将Computer作为基类),那么当您想要添加此类功能时,就不需要向层次结构添加任何新功能。
如果你不喜欢参观A,参观B。。。"模式",不用担心:这只是一个微不足道的实现,您不需要它。基本上,在实际实现中,您使用访问函数的模板专用化。
希望这能有所帮助,我为此付出了很多努力:)
虚拟函数要正确工作,需要具有完全相同的"签名"(相同的参数和相同的返回类型)。否则,你只会得到一个"新成员函数",这不是你想要的。
这里真正的问题是"调用代码如何知道它需要额外的信息"。
你可以用几种不同的方法来解决这个问题——第一种方法是总是在const vector <int>& additionalInfo
中传递,无论是否需要。
如果这不可能,因为除了FeatureC
之外没有任何additionalInfo
,您可以有一个"可选"参数,这意味着使用指向向量(vector<int>* additionalInfo
)的指针,当值不可用时,该指针为NULL。
当然,如果additionalInfo
是一个可以存储在FeatureC类中的值,那么它也会起作用。
另一种选择是将基类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; }
};
然后让你的循环像这样:
for (int i = 0; i < features.size(); ++i) {
if (features[i]->useAdditionalInfo())
{
res += features[i]->getValue(v, additionalInfo);
}
else
{
res += features[i]->getValue(v);
}
}
- 从类继承时,继承的类是否会通过父类重新定义继承的变量
- 从父类方法返回子类对象
- c++, 在子类中,如何在没有对象的情况下访问父类的方法?
- 将父类对象强制转换为子类的问题
- 在运行时选择父类的实现
- 如何在C++子类中访问父类的私有变量
- 将父类的子类的数据复制到具有相同父类的另一个类
- 父类的私有函数会导致对具有相同名称和相似参数的子类中的公共函数的不明确调用
- C++ 将子类的对象添加到父类的向量中
- 两个父类的构造函数的序列
- 为什么我的子类不继承父类的字符串?
- C++调用使用重写函数的父类函数
- 在父类中公开受保护的构造函数
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 如何使父类不重复已经执行的祖父方法
- 从模板化父类中的派生内部类访问受保护的成员变量
- Arduino在子类中使用父类方法
- 使用 Clang LibTooling 扫描C++在模板化父类中调用本地类的源
- 从父类的向量访问子类函数,而无需向下转换
- 几个类使用不同的api实现父类