在实现GUI时,我经常遇到一个难题。假设我有一个简单的小部件层次结构,其中一个主窗口有几个子小部件。子部件应该自己处理事件(例如,由用户输入产生的事件),还是将处理委托给父部件?这里有一个简单的例子:当点击"关闭"按钮时,窗口应该关闭。谁关上了窗户?窗口是否对按钮事件作出反应并自行关闭?或者按钮是否调用窗口上的Close方法?我使用的框架强烈暗示,首选的方式是让子部件向它们的父部件触发事件,父部件应该按照它认为合适的方式处理它们,因此窗口将像这样关闭(用c++ -ish伪代码):
void ChildWidget::OnClick()
{
GetParentWidget()->OnEvent(CreateClickEvent(*this));
}
void ParentWidget::OnEvent(Event& _event)
{
if (_event.Type() == EventTypeClick && _event.Id() == "close_button")
{
Close();
}
else if ...
...
}
然而,我担心它打破了单一职责原则,并导致父部件做了太多的工作。相反,我可以给子部件提供它们处理事件所需的对象,比如:
class CloseButtonWidget : public Widget
{
public:
CloseButtonWidget(Widget& widget_to_close)
: WidgetToClose_(&widget_to_close)
{}
void OnClick()
{
WidgetToClose->Close();
}
private:
Widget* WidgetToClose_;
};
我可以看到的缺点是,在许多情况下,我必须为这样的自处理小部件创建一个新类,然而,我经常必须为子小部件创建新类,所以这通常不是什么大问题。
我能想到的另一种方法是给子部件一个回调,比如:auto close_widget = new Widget();
// Suppose parent_widget is in scope
auto handler = CreateHandler(EventTypeClick, [parent_widget](){ parent_widget->Close(); });
close_widget->SetHandler(handler);
所以问题是——处理来自子部件的事件的最佳位置是什么?上述方法的优点和缺点是什么?也许还有其他更好的方法?我特别想听听"父部件处理一切"方案的优点,因为我发现很难看到任何优点。我想澄清一下,我想要一个以更"哲学"的方式解决问题的答案(比如什么是更好的设计),保持我使用的框架的首选方式和怪癖并不是很重要。
IMPO第一个方法是瓶颈。OS/框架做了很多工作来为您提供特定于小部件的事件,以避免您必须进行"获取"过程。你通过一个事件处理程序处理每个事件吗?听起来简直是浪费时间。
如果您希望以相同的方式处理一组事件(或从一组小部件引发的同一事件),请使用相同的事件处理程序。
例如,一个类似战舰的游戏:你有一个按钮网格,你以相同的方式处理网格中所有按钮的点击事件(一个按钮和其他按钮之间的唯一区别是它的坐标)。
如果您必须在每个小部件事件(或同一小部件的不同事件)中执行不同的操作,请使用特定于小部件的处理程序处理特定于小部件的事件。
正如您所注意到的,特定处理程序的问题在于它们的实现。使您过度重复代码。但是想想看,你不是在Java中!您有一等函数(通过原始函数指针或函子),而不是伪函数委托(又名内部类),它们只需要20行代码就可以编写单行操作。你在c++中,你有模板,lambda,函数和其他有用的工具来编写特定的处理程序,只需要一行简单而清晰的代码:)
如你所见,只要可能/简单,我更喜欢用你的第三种方法。