Avoiding 'instanceof' in Java

后端 未结 8 454
灰色年华
灰色年华 2020-12-07 11:14

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 clas

相关标签:
8条回答
  • 2020-12-07 11:34

    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()));
        }
    }
    
    0 讨论(0)
  • 2020-12-07 11:35

    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.

    0 讨论(0)
  • 2020-12-07 11:37

    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.

    0 讨论(0)
  • 2020-12-07 11:45

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-07 11:45

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

    0 讨论(0)
  • 2020-12-07 11:51

    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.

    0 讨论(0)
提交回复
热议问题