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

时间:2013-07-04 10:33:28

标签: c++ design-patterns polymorphism

我有一个带有纯虚方法的类功能。

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

4 个答案:

答案 0 :(得分:2)

您至少有两个选择:

  1. 不是将单个向量传递给getValue(),而是传递结构。在这个结构中,您可以在今天放置向量,明天可以放入更多数据。当然,如果程序的某些具体运行不需要额外的字段,那么计算它们的需要可能是浪费的。但是,如果你总是需要计算所有数据(即如果总会有一个FeatureC),它将不会造成性能损失。
  2. 传递给getValue()对具有获取必要数据的方法的对象的引用。该对象可以是计算机本身,也可以是一些更简单的代理。然后getValue()实现可以准确地请求他们需要的东西,并且它可以被懒惰地计算。在某些情况下,懒惰会消除浪费的计算,但是这样做的总体结构会因为必须调用(可能是虚拟的)函数来获取各种数据而产生一些小的不变开销。

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:1)

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

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

  

每个界面都可以采用其自然形状:当我们分开公共界面时   从定制界面,每个人都可以轻松地自然地采取形式   我想采取措施,而不是试图寻找迫使他们看起来的妥协   相同。通常,这两个接口需要不同数量的功能和/或   不同参数; [...]

这特别有用

  

在具有高成本变化的基类中

另一种在这种情况下非常有用的设计模式是访客模式。至于基类(以及整个层次结构)具有高成本变化时适用的NVI。您可以找到关于此设计模式的大量讨论,我建议您阅读Modern C ++(Alexandrescu)中的相关章节,(在旁边)让您对如何使用(非常易于使用)访客设施有很好的了解在loki

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

尝试解决以下问题:

  1. 基于模板的解决方案是否符合问题?
  2. 在调用函数时添加新的间接层是否可行?
  3. 会出现“推论” - “推论” - “.-”推论“ - ”调用函数“方法有帮助吗? (起初这可能看起来很奇怪,但是 想想像“cout&lt;&lt;&lt; arg&lt;&lt; arg&lt;&lt; arg&lt;&lt; endl”,其中 “endl”是“呼叫功能”)
  4. 您打算如何区分如何在Computer :: compute中调用该函数?
  5. 现在我们有了一些“理论”,让我们的目标是使用访客模式的实践:

    #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; }
};