什么更好-调用空方法还是使用多个接口?

4

我有几个类,它们都有一个名为Tool的基类。 在表单中,我有一个Tool引用,其中包含上述类的一个实例。 当鼠标按下事件发生在表单上时,我调用当前Tool方法,例如"CurrentTool.MethodWhenMouseDown()"。

大多数工具都有3个方法:

MethodWhenMouseDown()
MethodWhenMouseUp()
MethodWhenMouseMove()

但是有一两个班级只有:

MethodWhenMouseDown()

现在哪个更好:
1. 在工具中放置三种方法,对于不需要它们的类只需调用空方法。 2. 实现接口,例如IMouseMoveListener,只有需要在MouseMove事件发生时执行操作的类才会实现它。这样,如果MouseMove事件发生,我们就会询问:
if(CurrentTool is MouseMoveListener)
{
(CurrentTool as IMouseMoveListener).MethodWhenMouseMove();
}

附加信息:
该程序类似于Ms Paint - 工具有Brush、Bucket(无需MethodWhenMouseMove)、LineTool等。
在我的PaintForm中,我有一个抽象基类工具的引用,存储了派生类之一的实例。触发事件的是pictureBox。

您考虑过工具订阅的事件吗?- CodesInChaos

我认为在表单中编写一个方法,并在事件发生后调用该方法并调用CurrentTool的适当方法将是一个好的实践。例如:

void MouseMoveSubscriber(object sender, MouseEventArgs e)
{
CurrentTool.MethodWhenMouseMove(e);
}

我认为每次更改CurrentTool时订阅和取消订阅该方法是一种不好的做法。我也考虑过在表单中拥有所有工具引用,并且每个工具都会订阅事件,这样就不需要取消订阅了。但我认为这种方法的一个很大的缺点是每个工具都需要检查它是否是CurrentTool。
您对此有何看法?感谢您提供的帮助。

1
你是否考虑过工具订阅的事件? - CodesInChaos
不清楚为什么工具不能只订阅它想要监听的事件。 - Hans Passant
3个回答

2
性能不是问题(当用户点击时,调用空函数的开销并不重要),因此这实际上是关于编码的易用性和代码的清晰度/复杂度/可维护性。
所以我会尽可能地保持简单。
我会实现一个带有空实现的基类,因为这样干净简单。在派生类中需要最少的代码来获得所需的结果。这也是有道理的(如果你不覆盖点击的 upcall,你基本上是在说“当鼠标被点击时,我不想做任何事情”)。
下一个选项是为鼠标的 up/down/click 提供事件,并使派生类订阅这些事件(如果他们愿意)。使用事件是一种标准模式,但它的缺点是你必须处理难看的订阅和取消订阅调用。这样做的好处是,如果将它们设置为公共,任何人都可以处理这些事件,而不仅仅是派生类。
我会避免使用接口和转换 - 对我来说,这感觉像一种笨拙的方法 - 它只是将“空函数”方法分散到多个不同类型中,而不是一个简单的3个虚拟方法集。而且,你不仅需要调用这些方法并知道它们会起作用,还需要先进行很多类型转换和检查 - 这只会使事情变得混乱。
编辑 由于你添加了一些问题,我重新阅读了它,并想到了另一个可能性:创建一个基类工具,提供所有派生类都需要覆盖的虚拟MouseDown处理程序。所有常规工具都将从此派生。
另外,DragTool类可以作为一个中间类派生出来,添加所需的MouseMove和MouseUp处理程序,以用于您特殊的拖动工具。
即。
ToolBase (abstract MouseDown)
  |
  +- ClickTool1
  +- ClickTool2
  +- DragToolBase (abstract MouseMove + MouseUp)
      |
      +- DragTool1
      +- DragTool2

这意味着你的所有工具都不会有空实现。

我不同意“笨拙的方法”的观点。他所提到的接口是所谓的“角色接口”,这正是在支持多个角色的情况下使用它们的方式。 - Daniel Hilgarth
@DanielHilgarth:接口是一个很好的方法,当一组不相关的类需要支持共同的行为时,但是仅仅为了避免在少数直接相关的类中使用虚拟方法而发明一个全新的接口是过度设计。在我看来,这似乎会导致更多的代码、更复杂、更难理解和效率更低。它有什么优势呢?它似乎并不适用于这种特定情况。除非这个案例比问题所暗示的更复杂。 - Jason Williams
我同意:如果这些接口仅在此表单中使用,那么这些接口就过于复杂了。我假设还有其他地方需要IMouseMoveListener而不需要IMouseDownListener - Daniel Hilgarth

1

不知道你的情况,我会选择接口和基类的组合:
基类使用空虚拟方法实现所有接口。基类是一个纯粹的便利构造。如果一个工具类想要继承基类但不需要该方法,则不覆盖它。

在使用工具的代码中,你只需与接口一起工作。这样其他类就可以自由地直接实现你的接口。这样做可以获得最大的灵活性而不需要任何牺牲。

var mouseMoveListener = CurrentTool as IMouseMoveListener;
var mouseDownListener = CurrentTool as IMouseDownListener;
// ...

if(mouseMoveListener != null)
    mouseMoveListener.MethodWhenMouseMove();
if(mouseDownListener != null)
    mouseDownListener.MethodWhenMouseDown();

请注意:我在使用as时,只与is结合使用。

很抱歉,我没有理解第二段。如果一个基类实现了所有这些接口,那么所有派生类也会实现它们,所以我不认为CurrentTool不是IMouseMoveListener或IMouseDownListener的机会很大。 - user1593872
@user1593872:这个观察是正确的。但是其他类可能会决定只实现其中一个接口,而不从“ToolBase”派生。 - Daniel Hilgarth

0

这要看具体情况。但在您的特定情况下(UI事件),我认为拥有带有空处理程序(虚拟方法)的基类比许多接口更好。实际上,您所有的工具都将从某个ToolBase继承。而且,调用代码没有接口转换会更小更简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接