在许多标准Java库中(最值得注意的是AWT/Swing),您可以找到以下EventListener/Event pattern:
interface FooEventListener {
void fooDidBar(FooEvent e);
void fooDidBaz(FooEvent e);
void fooDidQux(FooEvent e);
}
并且,在FooEvent
中,您进一步发现
public class FooEvent {
static int FOO_DID_BAR = 1;
static int FOO_DID_BAZ = 2;
static int FOO_DID_QUX = 3;
// ...
public int getID(); // one of the above constants
}
这似乎是多余的。为什么做出这个决定?在调用的特定方法中以及在作为参数传递给该方法的事件中同时拥有此信息有什么好处?
例子:
- https://docs.oracle.com/javase/8/docs/api/java/awt/event/WindowEvent.html
- https://docs.oracle.com/javase/8/docs/api/javax/swing/event/ListDataListener.html
我认为原因是:
1) 所有 AWT/Swing 事件都扩展AWTEvent
,需要向其父构造函数提供事件 ID。这部分是遗留 AWT 代码库和 @MadProgrammer 提到的事件调度所必需的。
2)在侦听器中实现一个特定的回调方法比在单个方法中检查事件ID常量要简洁得多。实际上,在过去,我们只会从基于事件 id 的单个事件回调方法委托给私有方法。
3) 这允许使用 Adapter 类,这些类帮助仅实现仅对感兴趣的事件进行回调方法。
经过一番思考,我想我看到了双重编码事件子类型(示例中的Bar
、Baz
、Qux
)的其他优势,而不仅仅是它的历史重要性:
都有一个回调方法,可以更简洁地实现侦听器,因为不需要类似开关的语句来对每个子类型执行不同的操作。到目前为止,这在大多数事件处理代码中使用,并且非常明显。
从实现的角度来看,在事件对象本身中具有子类型id(
getId()
,可以重命名为非遗留事件的getType()
)成本相对较低(定义一堆常量并使它们与相应的子类型方法保持同步);并且当存在时允许有趣的调试策略:由特定模型触发的事件序列可以包含重建模型的所有更改所需的所有信息。从本质上讲,它使fooEvent.toString()
更加详细。
我进一步怀疑"完全独立的事件对象允许更容易调试"是历史双重编码的有力原因。