64

我有以下(可能是常见的)问题,目前它绝对让我感到困惑:

有几个生成的事件对象扩展了抽象类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。

是否有任何提示可以为这个问题实现“漂亮”的解决方案?

谢谢你的帮助!

4

8 回答 8

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);
    }
}
于 2011-05-27T21:28:46.013 回答
11

多态是你的朋友。

class DocumentGenerator {
   public void generate(DocumentEvent ev){}
   public void generate(MainEvent ev){}
   //... and so on
}

然后就

 DocumentGenerator dg = new DocumentGenerator();

 // ....
 dg.generate(event);

更新

许多人提出了反对意见,即您“必须在编译时知道事件的种类”。而且,是的,您显然必须知道在生成器部分的编译时要解释哪些事件,否则何时才能编写生成部分?

这些竞争示例使用命令模式,这很好,但意味着事件不仅要知道它们的表示的细节,还要知道如何打印它们的表示。这意味着每个类可能有两种他们敏感的需求变化:事件表示的变化,以及事件在打印中表示的方式的变化。

现在,例如,考虑需要将其国际化。在命令模式的情况下,您必须为n 个不同的事件类型访问n 个类并编写新的do方法。在多态的情况下,变化被局限在一个类中。

自然,如果您需要国际化一次,您可能需要多种语言,这促使您在命令模式案例中为每个类添加类似策略的东西,现在需要n 个类 × m种语言;同样,在多态情况下,您只需要一个策略和一个类。

选择任何一种方法都有理由,但声称多态方法是错误的就是不正确的。

于 2011-05-27T21:31:06.940 回答
8

每个事件都有一个功能,比如说做。每个子类都覆盖 do, to do (:P) 适当的操作。动态调度之后会做所有其他事情。您需要做的就是调用 event.do()

于 2011-05-27T21:26:01.550 回答
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);
    }
于 2011-05-27T21:46:55.567 回答
3

利用方法解析顺序有什么问题?

public void dispatchEvent(DocumentEvent e) {
    documentGenerator.gerenateDocument(event);
}

public void dispatchEvent(MailEvent e) {
    deliveryManager.deliverMail(event);
}

让 Java 完成匹配正确参数类型的工作,然后正确调度事件。

于 2011-05-27T21:28:14.150 回答
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机械地消除,而不必找出特定问题的解决方案。

于 2011-05-28T01:59:19.590 回答
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。

于 2011-05-28T07:18:59.727 回答
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()));
    }
}
于 2011-05-28T02:17:42.663 回答