Джава. Правильный шаблон для реализации слушателей - PullRequest
23 голосов
/ 04 июня 2010

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

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

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

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

, а

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

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

Ответы [ 5 ]

28 голосов
/ 04 июня 2010

Вместо того, чтобы у каждого 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) { 
                ... 
            }
        }
    }
}

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

12 голосов
/ 30 октября 2017

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

Вот шаги.

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

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

public class MyClass {

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

2. Создать установщик слушателя

Добавьте личную переменную-член слушателя и метод открытого сеттера в дочерний класс.

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. События прослушивателя триггера

Дочерний объект теперь может вызывать методы в интерфейсе слушателя. Обязательно проверьте на ноль, потому что там может не быть никого, кто слушает. (То есть родительский класс, возможно, не вызвал метод 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
    }
}
3 голосов
/ 04 июня 2010

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

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

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

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

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

1 голос
/ 04 июня 2010

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

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

0 голосов
/ 13 августа 2016

Попробуйте библиотеку 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's to all listeners, use 
   //    send(meow)
}

Генератор является поточно-ориентированным и эффективным (написание правильных генераторов является самой сложной частью). Это реализация идей в Java Dev. Журнал - квалифицированное прослушивание на Java (локальная копия)

...