几个类使用不同的api实现父类

several classes implement parent class with varying api

本文关键字:api 父类 实现 几个      更新时间:2023-10-16

我有一个类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,我可能以后必须再次修改所有这些签名。我想知道是否有一个更优雅的解决方案,或者是否有一种已知的设计模式可以让我进一步阅读。

您至少有两个选项:

  1. 与其将单个向量传递给getValue(),不如传递一个结构。在这个结构中,您今天可以放置向量,明天可以放置更多数据。当然,如果程序的某些具体运行不需要额外的字段,那么计算这些字段的需要可能是浪费。但是,如果您总是需要计算所有数据(即,如果总是有一个FeatureC),它不会带来性能损失
  2. 将对具有获取必要数据的方法的对象的引用传递给getValue()。这个对象可以是计算机本身,也可以是一些更简单的代理。然后getValue()实现可以准确地请求它们所需要的,并且可以延迟计算。在某些情况下,这种惰性将消除浪费的计算,但由于必须调用(可能是虚拟的)函数来获取各种数据,因此以这种方式进行计算的总体结构将施加一些较小的恒定开销

要求Feature类层次结构的用户根据类调用不同的方法会破坏多态性。一旦你开始做dynamic_cast<>(),你就知道你应该重新思考你的设计。

如果子类需要只能从调用方获得的信息,则应更改getValue()方法以获取additionalInfo参数,并在无关紧要的类中忽略该信息。

如果FeatureC可以通过调用另一个类或函数来获得additionalInfo,那么这通常是一种更好的方法,因为它限制了需要了解它的类的数量。也许数据可以从FeatureC通过其构造函数访问的对象中获得,也可以从单例对象获得,或者可以通过调用函数来计算。找到最好的方法需要对案例有更多的了解。

这个问题在C++编码标准(Sutter,Alexandrescu)的第39项中得到了解决,该项题为"考虑使虚拟函数非公共,使公共函数非虚拟。"

特别是,遵循非虚拟接口设计模式的动机之一(这就是项目的全部内容)被称为

每个接口都可以呈现其自然形状:当我们将公共接口分离时从自定义界面,每个都可以很容易地采用它自然的形式想要接受,而不是试图找到一个迫使他们去寻找的妥协完全相同的通常,这两个接口需要不同数量的功能和/或不同参数;[…]

这是特别有用的

在具有高更改成本的基类中

在这种情况下,另一种非常有用的设计模式是Visitor模式。至于NVI,它适用于基类(以及整个层次结构)具有高更改成本的情况。你可以找到很多关于这个设计模式的讨论,我建议你阅读现代C++(Alexandrescu)中的相关章节,它(侧面)让你对如何使用loki 中的(非常容易使用)访客设施有了很好的了解

我建议你阅读所有这些材料,然后编辑这个问题,这样我们就能给你一个更好的答案。我们可以想出所有类型的解决方案(例如,如果需要,使用一个额外的方法为类提供额外的参数),这可能不适合您的情况。

尝试解决以下问题:

  1. 基于模板的解决方案适合这个问题吗
  2. 在调用函数时添加一个新的间接层是否可行
  3. 一个"推式论证"-"推式论据"--"push argument"-"call function"方法有帮助吗?(一开始这可能看起来很奇怪,但是想想类似于"cout<<arg<<arg<"的东西,其中"endl"是"调用函数")
  4. 您打算如何区分如何调用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);
  }
}