Избегать «instanceof» в Java - PullRequest
63 голосов
/ 28 мая 2011

У меня есть следующая (может быть, общая) проблема, и на данный момент меня это озадачивает:

Существует несколько сгенерированных объектов событий, которые расширяют абстрактный класс Event, и я хочу разделить их на сессионные компоненты, например

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

}

Но в будущем может быть более двух типов событий, поэтому if-else будет длинным и, возможно, нечитаемым. Кроме того, я думаю, что instanceof на самом деле не является «лучшей практикой» в этом случае.

Я мог бы добавить абстрактный метод к типу Event и сделать так, чтобы они разделялись, но затем я должен внедрить конкретные Сессионные Бины в каждую сущность.

Есть ли какой-нибудь намек, чтобы найти "симпатичное" решение этой проблемы?

Спасибо за любую помощь!

Ответы [ 8 ]

54 голосов
/ 28 мая 2011

Самый простой подход состоит в том, чтобы Событие предоставляло метод, который вы можете вызвать, чтобы Событие знало, что делать.

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);
    }
}
11 голосов
/ 28 мая 2011

Полиморфизм - твой друг.

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

Тогда просто

 DocumentGenerator dg = new DocumentGenerator();

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

Обновление

Некоторые люди высказали возражение, что вам «нужно знать, какие события происходят во время компиляции». И, да, вам явно необходимо знать, какие события вы интерпретируете во время компиляции части генератора, когда еще вы сможете написать генерирующую часть?

В этих конкурирующих примерах используется шаблон Command, что хорошо, но означает, что События должны знать детали не только своего представления, но и того, как напечатать своего представления. Это означает, что каждый класс может иметь два вида изменений требований, к которым они чувствительны: изменения в том, что представляют события, и изменения в том, как события представлены в печати.

Теперь рассмотрим, например, необходимость интернационализации этого. В случае с шаблонами команд вам нужно перейти к n классам для n различных типов событий и написать новые методы do . В случае полиморфизма изменения локализуются в одном классе.

Естественно, если вам нужно один раз интернационализировать, вам может понадобиться много языков, которые заставят вас добавить что-то вроде Стратегии к каждому классу в случае с шаблоном Command, требуя теперь n занятия и время; м языки; опять же, вам нужна только одна стратегия и один класс в случае полиморфизма.

Есть причины выбрать любой из этих подходов, но утверждать, что подход полиморфизма является неправильным просто неверным.

8 голосов
/ 28 мая 2011

У каждого события есть функция, скажем, до.Каждый подкласс переопределения делает, чтобы сделать (: P) соответствующее действие.Динамическая отправка делает все остальное потом.Все, что вам нужно сделать, это вызвать event.do ()

4 голосов
/ 28 мая 2011

У меня нет прав на комментирование, и я не знаю точного ответа.Но только я или какой-то ppl здесь предлагаем использовать перегрузку (которая происходит во время компиляции и, следовательно, просто генерирует ошибку компиляции) для решения этой проблемы?

Просто пример.Как видите, он не скомпилируется.

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);
    }
3 голосов
/ 28 мая 2011

В чем проблема использования порядка разрешения методов?

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

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

Пусть Java выполнит работу по сопоставлению правильного типа аргумента, затем просто отправит событие правильно.

2 голосов
/ 28 мая 2011

Вы можете зарегистрировать каждый из ваших классов-обработчиков для каждого типа события и выполнять диспетчеризацию, когда событие происходит следующим образом.

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.

2 голосов
/ 28 мая 2011

Это типичный вариант использования для типов сумм , также известных как теговые объединения.К сожалению, 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 механически, а не искать решение для конкретной проблемы.

1 голос
/ 28 мая 2011

Вы можете иметь интерфейс 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()));
    }
}
...