抽象类/接口中的空方法是否被视为一种好习惯?

时间:2019-08-23 10:45:16

标签: c++ abstract-class virtual-functions

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

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

这被认为是一种好习惯吗?

3 个答案:

答案 0 :(得分:4)

您的问题是基于观点的,但是我宁愿遵循这种方法来使用界面:

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

一些论点为何在评论中添加了更好的做法:

  • 这个想法是,接口应该包含尽可能少的断言,从而使更改的可能性降低,从而使其对继承该接口的人更加可靠。实现这些方法(尽管是空的)已经是一个断言,但是规模很小。
  • 在以后进行重构时,痛苦会减轻,因为它会引入更多具有多重继承的接口。

答案 1 :(得分:2)

这实际上取决于您要从方法中得到什么。声明接口时,通常将这些方法保留为纯虚拟方法,因为必须要求来实现这些方法,才能使类完全起作用。将它们标记为纯虚拟信号“您必须实现这一点。”

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

我认为您的界面不是这种情况,您应该遵循@πάνταῥεῖ的回答。或通过多重继承来做到这一点:

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作为接口并将其用于所有“所有输入法”,否则将鼠标和键盘输入组合在一起的方法会变得很奇怪。

最后的提示:只要您尝试编写简洁的代码,考虑每个用例比一些最佳实践更重要。

答案 2 :(得分:1)

如果您要对此严格一点的话,这确实违反了ISP(https://en.wikipedia.org/wiki/Interface_segregation_principle),因为您迫使子类依赖于它不使用的方法-但是通常,如果替代方法添加了它,在实践中还不错更加复杂。