Как объявлять и использовать события в Java - PullRequest
10 голосов
/ 12 октября 2009

У меня есть простой класс - назову его Animal. Я хотел бы запустить событие в классе Animal и обработать его в классе, где я создал экземпляр класса Animal. В обработчике событий я хочу передать целочисленное значение

Как мне сделать что-то простое?

Ответы [ 9 ]

21 голосов
/ 12 октября 2009

Предполагая, что передаваемое целое число является частью состояния класса Animal, идиоматический способ сделать это вместо написания большого количества вашего собственного кода - это запустить PropertyChangeEvent. Для этого вы можете использовать класс PropertyChangeSupport, сократив код до следующего:

public class Animal {
  // Create PropertyChangeSupport to manage listeners and fire events.
  private final PropertyChangeSupport support = new PropertyChangeSupport(this);
  private int foo;

  // Provide delegating methods to add / remove listeners to / from the support class.  
  public void addPropertyChangeListener(PropertyChangeListener l) {
    support.addPropertyChangeListener(l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l) {
    support.removePropertyChangeListener(l);
  }

  // Simple example of how to fire an event when the value of 'foo' is changed.
  protected void setFoo(int foo) {
    if (this.foo != foo) {
      // Remember previous value, assign new value and then fire event.
      int oldFoo = this.foo;
      this.foo = foo;
      support.firePropertyChange("foo", oldFoo, this.foo);
    }
  }
}

Наконец, Я бы не советовал использовать Observer / Observable, так как это делает код нечитаемым / трудным для отслеживания: вам постоянно приходится проверять тип аргумента, передаваемого в Observer используя instanceof перед его понижением, и трудно увидеть, какого типа событие ожидает конкретная реализация Observer, взглянув на определение его интерфейса. Гораздо приятнее определить конкретные реализации слушателя и события, чтобы избежать этого.

9 голосов
/ 12 октября 2009

Если вы хотите избежать наследования от базового класса, подобного java.util.Observable, используйте интерфейс, и пусть ваши наблюдаемые реализуют или делегируют методы интерфейса.

Вот наблюдаемый интерфейс:

public interface IObservable
{
        void addObserver(IObserver o);

        void deleteObserver(IObserver o);

        void notifyObservers(INotification notification);
}

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

import java.util.ArrayList;
import java.util.List;


public class Observable implements IObservable
{
        private List<IObserver> observers;

        @Override
        public synchronized void addObserver(IObserver o)
        {
                if (observers == null)
                {
                        observers = new ArrayList<IObserver>();
                }
                else if (observers.contains(o))
                {
                        return;
                }
                observers.add(o);
        }

        @Override
        public synchronized void deleteObserver(IObserver o)
        {
                if (observers == null)
                {
                        return;
                }
                int idx = observers.indexOf(o);
                if (idx != -1)
                {
                        observers.remove(idx);
                }
        }

        @Override
        public synchronized void notifyObservers(INotification notification)
        {
                if (observers == null)
                {
                        return;
                }
                for (IObserver o : observers)
                {
                        o.update(notification);
                }
        }

}

Реальная наблюдаемая может выглядеть так:

class Person implements IObservable
{
        private final IObservable observable = new Observable();

        @Override
        public void setFirstName(String firstName) throws Exception
        {
            if (firstName == null || firstName.isEmpty())
            {
                    throw new Exception("First name not set");
            }

            this.firstName = firstName;
            notifyObservers(new Notification(this, getFirstNamePropertyId()));
        }

    @Override
    public void addObserver(IObserver o)
    {
            observable.addObserver(o);
    }

    @Override
    public void deleteObserver(IObserver o)
    {
            observable.deleteObserver(o);
    }

    @Override
    public void notifyObservers(INotification notification)
    {
            observable.notifyObservers(notification);
    }

    private static final String FIRSTNAME_PROPERTY_ID = "Person.FirstName";

    @Override
    public String getFirstNamePropertyId()
    {
            return FIRSTNAME_PROPERTY_ID;
    }

}

Вот интерфейс наблюдателя:

public interface IObserver
{
        void update(INotification notification);
}

Наконец, вот интерфейс уведомления и базовая реализация:

public interface INotification
{
        Object getObject();

        Object getPropertyId();
}

public class Notification implements INotification
{
        private final Object object;
        private final Object propertyId;

        public Notification(Object object, Object propertyId)
        {
                this.object = object;
                this.propertyId = propertyId;
        }

        @Override
        public Object getObject()
        {
                return object;
        }

        @Override
        public Object getPropertyId()
        {
                return propertyId;
        }
}
5 голосов
/ 12 октября 2009

Простой интерфейс событий выглядит так:

public interface AnimalListener {
    public void animalDoesSomething(int action);
}

Animal необходимо управлять своими слушателями:

public class Animal {
    private final List<AnimalListener> animalListeners = new ArrayList<AnimalListener>()
    public void addAnimalListener(AnimalListener animalListener) {
        animalListeners.add(animalListener);
    }
}

Ваш класс создания Animal должен сделать это:

public class AnimalCreator implements AnimalListener {
    public void createAnimal() {
        Animal animal = new Animal();
        animal.addAnimalListener(this); // implement addListener in An
    }
    public void animalDoesSomething(int action) {
        System.ot.println("Holy crap, animal did something!");
    }
}

Теперь Animal может запускать события.

public class Animal {
    ....
    public void doSomething() {
        for (AnimalListener animalListener : animalListeners) {
            animalListener.animalDoesSomething(4);
        }
    }
}

Это похоже на большой код для чего-то такого простого, как «запуск событий», но, возможно, запуск событий вовсе не прост. :)

Конечно, существуют различные расширения этого простого механизма.

  • Я всегда заставляю своих слушателей событий расширяться java.util.EventListener.
  • Первым параметром для каждого метода прослушивателя должен быть источник события, т.е. public void animalDoesSomething(Animal animal, int action);.
  • Управление зарегистрированными слушателями и срабатыванием событий можно абстрагировать к некоторому абстрактному классу управления слушателями событий. Посмотрите на PropertyChangeSupport , чтобы понять, что я имею в виду.
3 голосов
/ 02 ноября 2013

Я полагаю, что самое простое решение из всех них было немного упущено ...

В 95% случаев вам не нужно больше, чем это:

public class Aminal extends Observable {
    public void doSomethingThatNotifiesObservers() {
        setChanged();
        notifyObservers(new Integer(42));
    }
}

Полагаю, у вас не будет автоматического бокса, поэтому я создал объект Integer, но отчасти класс Observable относится к JDK1.0, поэтому он должен присутствовать в вашей версии Java.

При автобоксировании (в Java 1.5 и более поздних версиях) вызов notifyObservers будет выглядеть следующим образом:

notifyObservers(42);

Чтобы перехватить отправленное событие, вам нужно реализовать интерфейс Observer:

public class GetInt implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof Integer) {
            Integer integer = (Integer)arg;
            // do something with integer
        }
    }
}

Затем вы добавите GetInt к классу Animal:

Animal animal = new Animal();
GetInt getint = new GetInt();
animal.addObserver(getint);

Это все стандартная Java, а класс Observable реализует всю необходимую вам обработку наблюдателя.

Если вам нужно иметь возможность вызывать Observable извне, используйте решение Стива МакЛауда, но по моему опыту вы захотите, чтобы ваш класс обрабатывал то, что он знает, и позволял другим классам взаимодействовать с ним через Observer интерфейсы.

Единственное, что вам нужно знать, это то, что вам нужно позвонить setChanged() до того, как notifyObservers() или Observable не отправит никаких событий.

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

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

Если вы хотите наблюдать за классом по каждому объекту, я бы рекомендовал использовать PropertyChangeSupport, как описано Адамски выше. Также есть VetoableChangeSupport, который вы можете использовать, если хотите, чтобы слушатели могли блокировать изменения.

3 голосов
/ 12 октября 2009

См. java.util.Observable

РЕДАКТИРОВАТЬ: Адамский PropertyChangeSupport подход кажется лучше Наблюдаемый, который я предложил.

2 голосов
/ 12 октября 2009

Классы Observer / Observable были в Java с первого дня. К сожалению, оригинальные дизайнеры немного облажались. Честно говоря, у них не было возможности учиться за 10 лет опыта Java ...

Я решаю вашу проблему с делегированием. У меня есть собственная реализация Observer / Observable - и я рекомендую это. Но вот подход, который работает:

import java.util.Observable;
import java.util.Observer;

public class Animal {

    private final ImprovedObservable observable = new ImprovedObservable();

    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    public void notifyObservers() {
        observable.notifyObservers();
    }

    public void doSomething() {
        observable.setChanged();
        observable.notifyObservers(new AnimalEvent());
    }


}

// simply make setChanged public, and therefore usable in delegation
class ImprovedObservable extends Observable {
    @Override
    public void setChanged() {
        super.setChanged();
    }
}

class AnimalEvent {
}
1 голос
/ 12 октября 2009

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

http://www.ibm.com/developerworks/java/library/j-aopwork6/index.html

0 голосов
/ 12 января 2013

Guava's EventBus - еще одна готовая альтернатива

0 голосов
/ 12 октября 2009

Существуют также прекрасные библиотеки Spring , которые предоставляют готовую структуру событий .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...