抽象 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),因为你强迫子类依赖于它不使用的方法——但通常如果替代方案增加了更多的复杂性。
假设您有一个用于状态机不同状态的基础 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),因为你强迫子类依赖于它不使用的方法——但通常如果替代方案增加了更多的复杂性。