Подтвердить что ты не робот

Избегание "instanceof" в Java

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

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

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

}

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

Я мог бы добавить абстрактный метод к типу Event и разделить его, но затем мне нужно ввести определенный сеанс Beans внутри каждого объекта.

Есть ли намек на достижение "довольно" решения этой проблемы?

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

4b9b3361

Ответ 1

Самый простой способ - предоставить Событию метод, который вы можете назвать так, чтобы 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);
    }
}

Ответ 2

Полиморфизм - ваш друг.

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

Тогда просто

 DocumentGenerator dg = new DocumentGenerator();

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

Обновление

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

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

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

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

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

Ответ 3

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

Ответ 4

У меня нет комментариев, и я не знаю точного ответа. Но только я или некоторые 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);
    }

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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.

Ответ 8

У вас может быть интерфейс Dispatcher, определенный как

interface Dispatcher {
    void doDispatch(Event e);
}

с такими реализациями, как DocEventDispatcher, MailEventDispatcher и т.д.

Затем определите a Map<Class<? extends Event>, Dispatcher>, с элементами типа (DocEvent, new DocEventDispatcher()). Тогда ваш метод отправки может быть уменьшен до:

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

Здесь 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()));
    }
}