抽象 class/interface 中的空方法是否被认为是一种好的做法?

Are empty methods in an abstract class/interface considered a good practice?

假设您有一个用于状态机不同状态的基础 class,该状态机具有用于鼠标、键盘、操纵杆等不同输入的方法。现在并非每个派生状态都将使用所有可能的方法输入的类型。如果基础 class 方法是纯虚拟的,每个派生状态 class 都需要始终实现其中的每一个。为了避免这种情况,我在基 class 中用一个空体声明了它们,并且只是覆盖了特定派生 class 使用的那些。如果 class 不使用特定输入类型,则会调用空基 class 方法。我将 currentState 存储在一个基础 class 指针中,只需将输入提供给它,而不必知道它实际上是哪个特定的派生状态,以避免不必要的转换。

class Base
{
  public:
    virtual void keyboardInput() {}
    virtual void mouseInput() {}
};

class Derived : public Base
{
  public:
    void keyboardInput()
    {
        // do something
    }

    // Derived doesnt use mouseInput so it doesn't implement it    
};

void foo(Base& base)
{
    base.keyboardInput();
    base.mouseInput();
}

int main()
{
    Derived der;
    foo(der);
}

这被认为是一种好的做法吗?

你的问题是基于意见的,但我宁愿按照这种方法来使用接口:

struct IBase {
    virtual void keyboardInput() = 0;
    virtual void mouseInput() = 0;
    virtual ~IBase() {}
};

class Base : public IBase {
  public:
    virtual void keyboardInput() override {}
    virtual void mouseInput() override {}
};

class Derived : public Base {
  public:
    void keyboardInput() override {
        // do something
    }

    // Derived doesnt use mouseInput so it doesn't implement it    
};

int main() {
    std::unique_ptr<IBase> foo  = new Derived();

    foo->keyboardInput();
    foo->mouseInput();

    return 0;
}

从评论中添加了一些论点,为什么这是更好的做法:

  • 想法是接口应包含尽可能少的断言,使其更不可能更改,从而使继承它的人更加可靠。实现这些方法,尽管是空的,但已经是一个断言,无论多么小。
  • 以后的重构会更轻松,引入更多具有多重继承的接口。

这真的取决于你想从这些方法中得到什么。当声明一个接口时,通常这些方法是纯虚拟的,因为它们 需要 实现 class 才能工作。将它们标记为纯虚拟信号 "You have to implement this.".

但是,有时有些方法可能什么都不做,并且对于它们什么也不做的所有可能实现都是有效的。这不是很常见,但有可能。

我不认为你的界面是这样的,你应该按照@πìντα ῥεῖ 的回答。或者通过多重继承来实现:

class MouseInput {
  public:
    virtual void mouseInput() = 0;
}

class KeyboardInput {
  public:
    virtual void keyboardInput() = 0;
}

class Derived : public KeyboardInput
{
  public:
    virtual void keyboardInput() override
    {
        // do something
    }
};

class AllInput : public KeyboardInput, public MouseInput
{
  public:
    virtual void keyboardInput() override
    {
        // do something
    }
    virtual void mouseInput() override
    {
        // do something
    }
};

这样做的好处是您可以使用明确说明它们适用于一种输入的方法:

void doSomethingMouseIsh(MouseInput* input);

缺点是结合鼠标和键盘输入的方法会变得很奇怪,除非你有 InterfaceAllInput 作为界面并将它用于所有 "all input methods"

最后说明:只要您尝试编写干净的代码,考虑每个用例比一些最佳实践更重要。

如果你要严格遵守,这确实违反了 ISP (https://en.wikipedia.org/wiki/Interface_segregation_principle),因为你强迫子类依赖于它不使用的方法——但通常如果替代方案增加了更多的复杂性。