64

I have the following (maybe common) problem and it absolutely puzzles me at the moment:

There are a couple of generated event objects which extends the abstract class Event and I want to divide them to Session Beans, like

public void divideEvent(Event event) {
    if (event instanceof DocumentEvent) {
        documentGenerator.gerenateDocument(event);
    } else if (event instanceof MailEvent) {
        deliveryManager.deliverMail(event);
        ...
    }
    ...

}

But there could be more than two event types in future, so the if-else will be long and maybe unreadable. Additionally I think instanceof is not really "best practice" in this case.

I could add an abstract method to the Event type and have them divide itself but then I have to inject the specific Session Beans within each entity.

Is there any hint to achieve a "pretty" solution for this problem?

Thanks for any help!

fnst
  • 5,574
  • 4
  • 23
  • 18

8 Answers8

54

The simplest approach is to have the Event provide a method you can call so the Event knows what to do.

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);
    }
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    Why would the event know what kind of Document to generate? – Charlie Martin May 27 '11 at 21:54
  • 2
    An event wouldn't but a DocumentEvent might know something about the document to be generated. ;) The question is fairly abstract, and this approach won't work in all cases, but it if it does work, it would be the simplest, easiest to extend solution. – Peter Lawrey May 27 '11 at 22:07
  • 11
    What you've demonstrated is a very good example of a **Visitor Pattern**. – Buhake Sindi May 28 '11 at 06:04
  • 3
    @The Elite, it's similar to the visitor pattern, but not the same. The visitor pattern is for plugging in behavior based on a closed object graph. IMO, this is closer to the standard `ActionListener.actionPerformed` pattern. – Kirk Woll May 29 '11 at 03:47
  • 7
    I'm nervous about this. This seems like too much mixing of a simple POJO data object with logic. Events stop becoming bags of data and start to become delegates to other methods – TheLQ Jun 03 '11 at 03:09
11

Polymorphism is your friend.

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

Then just

 DocumentGenerator dg = new DocumentGenerator();

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

Update

A number of people have raised the objection that you "have to know the kinds of event at compile time." And, yes, you clearly have to know what events you're interpreting at compile time of the generator part, when else would you be able to write the generating part?

These competing examples use Command pattern, which is fine, but means Events have to know the details not just of their representation but of how to print their representation. That means each class may have two kinds of requirements changes to which their sensitive: changes in what events represent, and changes in the way the events are represented in print.

Now, consider, for example, needing to internationalize this. In the Command-pattern case, you have to go to n classes for n different Event types and write new do methods. In the polymorphism case, the changes are localized to one class.

Naturally if you need to internationalize once, you may need many languages, which drive you to adding something like a Strategy to each class in the Command-pattern case, requiring now n classes × m languages; again, you need only have one strategy and one class in the polymorphism case.

There are reasons to choose either approach, but to claim the polymorphism approach is wrong is just incorrect.

Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • 4
    This looks good at first sight but probably doesn't fit since it needs the type to be known at compile time, which is obviously not the case in the situation in question. – x4u May 27 '11 at 22:37
  • 4
    More specifically, the above won't compile. the `dg.generate(event)` call will fail because there is no method matching `generate(Event)` on `DocumentGenerator`. Polymorphism doesn't work on method arguments like this. – David Harkness May 28 '11 at 00:57
  • 1
    This answer **is wrong**. `instanceof` checks for the object type at runtime, while overloading methods reacts to the different types at compile-time. – blubb Jun 09 '11 at 11:07
  • Agreed with the others saying this answer is wrong - which it is. The overloaded method to call is determine AT COMPILE TIME. You would have to explicit cast your Event object to the correct subclassed one (which defeats the purpose) for this to work. The "update" part of your answer does not change this fact. – GreenieMeanie Jun 13 '11 at 20:58
  • It is indeed very wrong, and it's not even polymorphism but overloading. There is no runtime dispatch here. – KeatsPeeks Aug 12 '15 at 13:15
8

Each event has a function, say do. Each subclass overrides do, to do (:P) the appropriate action. Dynamic dispatch does everything else afterwards. All you need to do, is call event.do()

George Kastrinis
  • 4,924
  • 4
  • 29
  • 46
4

I have no commenting rights and i dont know the exact answer. But is it just me or some ppl here suggest using overloading (which happens at compile time and therefore just generate compile error) to solve this problem?

Just an example. As you see, it will not compile.

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);
    }
user124
  • 1,632
  • 1
  • 11
  • 11
  • As one of the people who suggested overloading a method, I don't see where in my solution (or in others) the compiler would complain, as you seem to imply. What error do you think it would generate, and why? (You can edit your answer to provide more details.) – Giulio Piancastelli May 27 '11 at 22:03
  • 3
    I was referring strictly to overloading. At a first glance your answer looked like overloading solution. Maybe i am missing something there. If one have reference to Event obj, he cant call method x if there is only two overloads: x(MailEvent) and x(DocEvent). There is simply no match @ compile time. – user124 May 27 '11 at 22:19
  • You are right, and adding a `dispatchEvent(Event e)` wouldn't solve the problem, as that method would be _always_ called if you pass an `Event` reference as argument. But, on the other hand, if you are able to pass a reference to the specific event class, e.g. `d.dispatchEvent(new DocEvent())`, oveloading works fine. – Giulio Piancastelli May 27 '11 at 22:47
  • 1
    @Giulio - If you were going to do that, you wouldn't call a dispatcher at that point--you'd call the method it eventually gets dispatched to directly. The point of a dispatcher is to be able to pass an unknown instance and have it Do The Right Thing (tm). – David Harkness May 28 '11 at 00:59
3

What's the problem with exploiting the method resolution order?

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

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

Let Java do the work of matching the correct argument type, then just dispatch the event properly.

Giulio Piancastelli
  • 15,368
  • 5
  • 42
  • 62
  • 3
    As others (@bigoldbrute in his own answer, @x4u in a comment to @Charlie Martin's answer) have noted, this kind of solution works only if the argument's type is known at compile time, i.e. the argument is not a reference to the parent abstract `Event` class. (Oh, _that's_ the problem with exploiting method resolution order!) – Giulio Piancastelli May 27 '11 at 22:55
2

This is a typical use case for Sum types, also known as tagged unions. Unfortunately, Java does not support them directly, so they have to be implemented using some variation of the visitor pattern.

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);
    }
}

Here we have defined our EventDivider, now to provide a dispatch mechanism:

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());
}

Here I used maximum possible separation of concerns so that responsibility of each class and interface is one and only one. In real life projects DocumentEventImpl, DocumentEvent implementation and DocumentEvent interface declaration are usually merged into a single class DocumentEvent, but that introduces circular dependencies and forces some dependencies between concrete classes (and as we know, one should prefer to depend on interfaces).

Additionally, void should usually be replaced with a type parameter to represent the result type, like this:

interface EventVisitor<R> {
    R visitDocumentEvent(DocumentEvent event);
    ...
}

interface Event {
    <R> R accept(EventVisitor<R> visitor);
}

This allows one to use stateless visitors, which are very nice to deal with.

This technique allows to (almost?) always eliminate instanceof mechanically rather than having to figure out a problem-specific solution.

Rotsor
  • 13,655
  • 6
  • 43
  • 57
2

You could register each of your handler classes against each event type, and perform dispatch when event happens like this.

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);
}

And then get your specific handlers to implement the interface, and register those handlers with the EventRegister.

Yohan Liyanage
  • 6,840
  • 3
  • 46
  • 63
1

You could have a Dispatcher interface, defined like

interface Dispatcher {
    void doDispatch(Event e);
}

with implementations like DocEventDispatcher, MailEventDispatcher, etc.

Then define a Map<Class<? extends Event>, Dispatcher>, with entries like (DocEvent, new DocEventDispatcher()). Then your dispatch method could be reduced to:

public void divideEvent(Event event) {
    dispatcherMap.get(event.getClass()).doDispatch(event);
}

Here's a unit test:

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()));
    }
}
Ray
  • 4,829
  • 4
  • 28
  • 55
  • This can not work: `dispatcherMap.get(event.getClass())` will always return `null` – Rotsor May 28 '11 at 03:22
  • you're right--should have said "entries like `(DocEvent.class, new DocEventDispatcher())`" – Ray May 28 '11 at 12:06
  • This makes sense now. However, this is not avoiding `instanceof`, but rather hiding it. You still make use of runtime type information, which is the bad thing about `instanceof`. – Rotsor May 28 '11 at 21:53
  • Which of these solutions does not depend on runtime type information? – Ray May 28 '11 at 22:37
  • All those using virtual methods for dispatch. – Rotsor May 28 '11 at 22:46