4

我曾经写过这样的接口类:

class SomeInterface
{
public:
    virtual void doThings() = 0;
    virtual void run() = 0;
};

class OtherInterface
{
public:
    virtual void doStuff() = 0;
    virtual void run() = 0;
};

这意味着我可以轻松地继承几个接口并像这样使用它

class ConcreteClass : public SomeInterface, public OtherInterface
{
public:
    virtual void doThings() { std::cout << "do things" << std::endl; }
    virtual void doStuff() { std::cout << "do stuff" << std::endl; }
    virtual void run() { std::cout << "do run" << std::endl; }
};

int main()
{
    ConcreteClass myObject;
    myObject.run();
    return 0;
}

但我最近读到了关于非虚拟界面的成语(像这样的东西http://www.gotw.ca/publications/mill18.htm),按照这些指导方针,我应该像这样编写我的界面:

class SomeInterface
{
public:
    void doThings() { doThingsImplementation(); }
    void run() { runImplementation(); }
private:
    virtual void doThingsImplementation() = 0;
    virtual void runImplementation() = 0;
};

class OtherInterface
{
public:
    void doStuff() { doStuffImplementation(); }
    void run() { runImplementation(); }
private:
    virtual void doStuffImplementation() = 0;
    virtual void runImplementation() = 0;
};

这意味着其余代码现在看起来像这样

class ConcreteClass : public SomeInterface, public OtherInterface
{
private:
    virtual void doThingsImplementation() { cout << "do things" << endl; }
    virtual void doStuffImplementation() { cout << "do stuff" << endl; }
    virtual void runImplementation() { cout << "run" << endl; }
};

int main()
{
    ConcreteClass myObject;
    myObject.run();
    return 0;
}

...除了现在它没有编译,因为对的调用run()变得模棱两可(error: request for member ‘run’ is ambiguous)。过去接口中无害的重叠现在会导致名称冲突。

重读非虚拟接口习语的论点,我觉得问题在于我将它应用于纯虚函数的虚函数。是这样吗?我应该只将 NVI 成语用于非纯虚函数吗?

编辑1:有人说该run()函数可能在它自己的接口类中。这实际上很有意义:要么两者run()具有相同的含义并且应该考虑接口,要么它们具有不同的含义并且我们希望编译器说事情是模棱两可的。当使用纯虚函数不存在的 NVI 时,这仍然给我们留下了菱形继承问题

编辑2:事实证明,如果我在其自己的接口类中将其分解为纯虚函数,并run()从中继承,事情实际上并没有我想象的那么好。如果我引入(从两个接口类继承)并从中继承,我可以调用但我不能从 a 多态地使用它(同样,调用被认为是模棱两可的)。SomeInterfaceOtherInterfaceCombinedInterfaceConcreteClassrun()myObjectCombinedInterface*

结论:我的结论是 1) 接口不应该重叠,或者被分解。2)接口类之间的继承应该总是虚拟的

4

2 回答 2

2

您只需要隐藏派生类中的冲突名称。

class ConcreteClass : public SomeInterface, public OtherInterface
{
  public:
    void run() { runImplementation(); }
  // rest of the class

阴影不会破坏 NVI 的目的吗?

哦,但是NVI 的目的是什么它不是为了它自己的缘故,我们通常也会在非虚拟部分添加前操作和后操作。现在,如果 and 中的前操作和后操作不同SomeInterfaceOtherInterface那么您可以选择在派生类中以某种方式组合它们。

class ConcreteClass : public SomeInterface, public OtherInterface
{
  public:
   void run() {
     SomeInterface::preRun();
     OtherInterface::preRun();
     runImplementation() // etc      
 }

但是,如果它们一开始是相同的,那么也许run应该在自己的接口中,实际上由两者继承SomeInterfaceOtherInterface。(实际上是因为否则仍然有两个副本run,每个副本都在其自己的基础子对象中)。

于 2020-10-23T08:16:58.500 回答
0

您需要指定要调用哪个接口的运行,因为通常它们会做不同的事情。

int main()
{
    ConcreteClass myObject;
    myObject.SomeInterface::run();
    // or
    myObject.OtherInterface::run();
    return 0;
}
于 2020-10-23T08:20:03.597 回答