我有以下(可能是常见的)问题,目前绝对令我困惑:
有几个生成的事件对象扩展了抽象类Event
,我想把它们分成会话Bean,比如
public void divideEvent(Event event) {
if (event instanceof DocumentEvent) {
documentGenerator.gerenateDocument(event);
} else if (event instanceof MailEvent) {
deliveryManager.deliverMail(event);
...
}
...
}
但是将来可能会有两种以上的事件类型,所以if-else会很长并且可能不可读。另外我认为instanceof
在这种情况下并不是真正的“最佳实践”。
我可以向Event
类型添加一个抽象方法,让它们自行划分,但是我必须在每个实体中注入特定的会话Bean。
是否有任何提示可以解决此问题的“漂亮”解决方案?
感谢您的帮助!
答案 0 :(得分:54)
最简单的方法是让Event提供一个可以调用的方法,以便Event知道该怎么做。
interface Event {
public void onEvent(Context context);
}
class DocumentEvent implements Event {
public void onEvent(Context context) {
context.getDocumentGenerator().gerenateDocument(this);
}
}
class MailEvent implements Event {
public void onEvent(Context context) {
context.getDeliveryManager().deliverMail(event);
}
}
class Context {
public void divideEvent(Event event) {
event.onEvent(this);
}
}
答案 1 :(得分:11)
多态性是你的朋友。
class DocumentGenerator {
public void generate(DocumentEvent ev){}
public void generate(MainEvent ev){}
//... and so on
}
然后只是
DocumentGenerator dg = new DocumentGenerator();
// ....
dg.generate(event);
<强>更新强>
许多人提出反对意见,“你必须在编译时了解事件的种类。”而且,是的,您显然必须知道您在生成器部件的编译时要解释的事件,否则您何时可以编写生成部件?
这些竞争示例使用命令模式,这很好,但意味着事件不仅要知道它们的表示细节,还要知道如何打印它们的表示。这意味着每个类可能有两种需求变化,它们的敏感性:事件所代表的变化,以及事件在打印中的表示方式的变化。
现在,考虑一下,需要对此进行国际化。在Command-pattern的情况下,您必须转到 n 类来获取 n 不同的事件类型并编写新的 do 方法。在多态性的情况下,更改被本地化为一个类。
当然,如果您需要进行一次国际化,您可能需要多种语言,这会促使您在Command-pattern情况下向每个类添加类似策略的内容,现在需要 n < / em> classes× m 语言;再次,在多态性情况下,您只需要一个策略和一个类。
有理由选择任何一种方法,但声称多态方法错误只是不正确。
答案 2 :(得分:8)
每个事件都有一个功能,比如do。 每个子类都覆盖do,以执行(:P)适当的操作。 动态调度之后会执行其他所有操作。 您需要做的就是调用event.do()
答案 3 :(得分:4)
我没有评论权,我也不知道确切的答案。但这只是我或者一些人在这里建议使用重载(这在编译时发生,因此只是生成编译错误)来解决这个问题吗?
只是一个例子。如你所见,它不会编译。
package com.stackoverflow;
public class Test {
static abstract class Event {}
static class MailEvent extends Event {}
static class DocEvent extends Event {}
static class Dispatcher {
void dispatchEvent(DocEvent e) {
System.out.println("A");
}
void dispatchEvent(MailEvent e) {
System.out.println("B");
}
}
public static void main(String[] args) {
Dispatcher d = new Dispatcher();
Event e = new DocEvent();
d.dispatchEvent(e);
}
答案 4 :(得分:3)
利用方法解析顺序有什么问题?
public void dispatchEvent(DocumentEvent e) {
documentGenerator.gerenateDocument(event);
}
public void dispatchEvent(MailEvent e) {
deliveryManager.deliverMail(event);
}
让Java完成匹配正确参数类型的工作,然后正确地调度事件。
答案 5 :(得分:2)
这是Sum types的典型用例,也称为已标记的联合。遗憾的是,Java并不直接支持它们,因此必须使用访问者模式的某些变体来实现它们。
interface DocumentEvent {
// stuff specific to document event
}
interface MailEvent {
// stuff specific to mail event
}
interface EventVisitor {
void visitDocumentEvent(DocumentEvent event);
void visitMailEvent(MailEvent event);
}
class EventDivider implements EventVisitor {
@Override
void visitDocumentEvent(DocumentEvent event) {
documentGenerator.gerenateDocument(event);
}
@Override
void visitMailEvent(MailEvent event) {
deliveryManager.deliverMail(event);
}
}
这里我们定义了EventDivider
,现在提供一个调度机制:
interface Event {
void accept(EventVisitor visitor);
}
class DocumentEventImpl implements Event {
@Override
void accept(EventVisitor visitor) {
visitor.visitDocumentEvent(new DocumentEvent(){
// concrete document event stuff
});
}
}
class MailEventImpl implements Event { ... }
public void divideEvent(Event event) {
event.accept(new EventDivider());
}
在这里,我使用了最大可能的关注点分离,以便每个类和接口的责任是唯一的。在现实生活中,项目DocumentEventImpl
,DocumentEvent
实现和DocumentEvent
接口声明通常合并为单个类DocumentEvent
,但这会引入循环依赖关系并强制在具体类之间存在一些依赖关系(我们知道,人们应该更喜欢依赖接口。
此外,void
通常应替换为类型参数以表示结果类型,如下所示:
interface EventVisitor<R> {
R visitDocumentEvent(DocumentEvent event);
...
}
interface Event {
<R> R accept(EventVisitor<R> visitor);
}
这允许人们使用无状态访问者,这非常适合处理。
这种技术允许(几乎?)总是机械地消除instanceof
,而不是必须找出特定问题的解决方案。
答案 6 :(得分:2)
您可以针对每种事件类型注册每个处理程序类,并在事件发生时执行调度。
class EventRegister {
private Map<Event, List<EventListener>> listerMap;
public void addListener(Event event, EventListener listener) {
// ... add it to the map (that is, for that event, get the list and add this listener to it
}
public void dispatch(Event event) {
List<EventListener> listeners = map.get(event);
if (listeners == null || listeners.size() == 0) return;
for (EventListener l : listeners) {
l.onEvent(event); // better to put in a try-catch
}
}
}
interface EventListener {
void onEvent(Event e);
}
然后让您的特定处理程序实现该接口,并使用EventRegister注册这些处理程序。
答案 7 :(得分:1)
您可以拥有Dispatcher
接口,定义为
interface Dispatcher {
void doDispatch(Event e);
}
使用DocEventDispatcher
,MailEventDispatcher
等实现
然后定义Map<Class<? extends Event>, Dispatcher>
,其中包含(DocEvent, new DocEventDispatcher())
等条目。然后您的调度方法可以简化为:
public void divideEvent(Event event) {
dispatcherMap.get(event.getClass()).doDispatch(event);
}
这是一个单元测试:
public class EventDispatcher {
interface Dispatcher<T extends Event> {
void doDispatch(T e);
}
static class DocEventDispatcher implements Dispatcher<DocEvent> {
@Override
public void doDispatch(DocEvent e) {
}
}
static class MailEventDispatcher implements Dispatcher<MailEvent> {
@Override
public void doDispatch(MailEvent e) {
}
}
interface Event {
}
static class DocEvent implements Event {
}
static class MailEvent implements Event {
}
@Test
public void testDispatcherMap() {
Map<Class<? extends Event>, Dispatcher<? extends Event>> map = new HashMap<Class<? extends Event>, Dispatcher<? extends Event>>();
map.put(DocEvent.class, new DocEventDispatcher());
map.put(MailEvent.class, new MailEventDispatcher());
assertNotNull(map.get(new MailEvent().getClass()));
}
}