Ява. Правильный образец для реализации слушателей

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

class Elephant {
  public void addListener( ElephantListener listener ) { ... }
}

но у меня будет много таких ситуаций. То есть, у меня также будет объект Tiger, у которого будет TigerListener s. Теперь TigerListener и ElephantListener сильно отличаются:

interface TigerListener {
  void listenForGrowl( Growl qrowl );
  void listenForMeow( Meow meow );
}

а

interface ElephantListener {
  void listenForStomp( String location, double intensity );
}

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

Ответ 1

Вместо каждого Listener, имеющего специальные методы для каждого типа событий, вы можете его отправить, измените интерфейс, чтобы принять общий Event класс. Затем вы можете подклассифицировать Event на определенные подтипы, если вам нужно, или иметь такое состояние, как double intensity.

TigerListener и ElephentListener становятся

interface TigerListener {
    void listen(Event event);
}

Фактически, вы можете затем повторно преобразовать этот интерфейс в простой Listener:

interface Listener {
    void listen(Event event);
}

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

class TigerListener implements Listener {
    @Overrides
    void listen(Event event) {
        if (event instanceof GrowlEvent) {
            //handle growl...
        }
        else if (event instance of MeowEvent) {
            //handle meow
        }
        //we don't care about any other types of Events
    }
}

class ElephentListener {
    @Overrides
    void listen(Event event) {
        if (event instanceof StompEvent) {
            StompEvent stomp = (StompEvent) event;
            if ("north".equals(stomp.getLocation()) && stomp.getDistance() > 10) { 
                ... 
            }
        }
    }
}

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

Ответ 2

Я думаю, что вы делаете это правильно, поскольку ваши интерфейсы имеют семантическое значение и выражают то, что они слушают (например, рычание и мяу, а не топает). При использовании общего подхода вы можете повторно использовать код вещания, но вы можете потерять удобочитаемость.

Например, существует java.beans.PropertyChangeSupport, который является утилитой для реализации Oberservers, прослушивающих изменения значений. Он выполняет широковещательную рассылку, но вам все равно нужно реализовать этот метод в своем классе домена и делегировать объект PropertyChangeSupport. Методы обратного вызова сами по себе бессмысленны, а события, передаваемые в эфир, основаны на строках:

public interface PropertyChangeListener extends java.util.EventListener {
     void propertyChange(PropertyChangeEvent evt);
}

Другой - java.util.Observable, который обеспечивает механизм вещания, но это также не лучшая вещь imho.

Мне нравится ElephantListener.onStomp()

Ответ 3

Это более общий ответ для людей, которые приезжают сюда, просто желая сделать слушателя. Я обобщаю Создание пользовательских Listeners из CodePath. Прочтите эту статью, если вам нужно больше объяснений.

Вот шаги.

1. Определить интерфейс

Это в дочернем классе, который должен связываться с каким-то неизвестным родителем.

public class MyClass {

    // interface
    public interface MyClassListener {
        // add whatever methods you need here
        public void onSomeEvent(String title);
    }
}

2. Создание сеттера прослушивателя

Добавьте дочернюю переменную-член и переменную public setter в дочерний класс.

public class MyClass {

    // add a private listener variable
    private MyClassListener mListener = null;

    // provide a way for another class to set the listener
    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }


    // interface from Step 1
    public interface MyClassListener {
        public void onSomeEvent(String title);
    }
}

3. События прослушивателя триггеров

Теперь дочерний объект может вызывать методы на интерфейсе прослушивателя. Обязательно проверьте значение null, потому что, возможно, никто не будет слушать. (То есть родительский класс, возможно, не вызвал метод setter для нашего слушателя.)

public class MyClass {

    public void someMethod() {
        // ...

        // use the listener in your code to fire some event
        if (mListener != null) 
            mListener.onSomeEvent("hello");
    }


    // items from Steps 1 and 2

    private MyClassListener mListener = null;

    public void setMyClassListener(MyClassListener listener) {
        this.mListener = listener;
    }

    public interface MyClassListener {
        public void onSomeEvent(String myString);
    }
}

4. Внедрить обратные вызовы слушателя в родительском

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

Пример 1

public class MyParentClass {

    private void someMethod() {

        MyClass object = new MyClass();
        object.setMyClassListener(new MyClass.MyClassListener() {
            @Override
            public void onSomeEvent(String myString) {
                // handle event
            }
        });
    }
}

Пример 2

public class MyParentClass implements MyClass.MyClassListener {

    public MyParentClass() {
        MyClass object = new MyClass();
        object.setMyClassListener(this);
    }

    @Override
    public void onSomeEvent(String myString) {
        // handle event
    }
}

Ответ 4

Другими параметрами являются "Белая доска" . Это отключает издателя и подписчика друг от друга, и ни один из них не будет содержать какой-либо код вещания. Они оба просто используют механизм обмена сообщениями для pub/sub, и ни одно из них не имеет прямого подключения к другому.

Это общая модель обмена сообщениями на платформе OSGi.

Ответ 5

Попробуйте java kiss, и вы сделаете это быстрее и правильнее.

import static kiss.API.*;

class Elephant {
  void onReceiveStomp(Stomp stomp) { ... }
}

class Tiger {
  void onReceiveMeow(Meow meow) { ... }
  void onReceiveGrowl(Growl growl) { ... }
}

class TigerMeowGenerator extends Generator<Meow> {
   // to add listeners, you get: 
   //    addListener(Object tiger); // anything with onReceiveMeow(Meow m);
   //    addListener(meow->actions()); // any lambda
   // to send meow to all listeners, use 
   //    send(meow)
}

Генератор является потокобезопасным и эффективным. Это реализация идей в JDJ - квалифицированный прослушивание в Java