Шаблон наблюдателя в Java

Шаблон наблюдателя в Java

1. обзор

В этой статье мы собираемся описать шаблон Observer и взглянуть на несколько альтернатив реализации Java.

2. Что такое паттерн наблюдателя?

Наблюдатель - это модель поведения. Он определяет связь между объектами:observable иobservers. An observable is an object which notifies observers about the changes in its state.

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

Посмотрим, как мы можем реализовать это сами.

Во-первых, давайте определим классNewsAgency:

public class NewsAgency {
    private String news;
    private List channels = new ArrayList<>();

    public void addObserver(Channel channel) {
        this.channels.add(channel);
    }

    public void removeObserver(Channel channel) {
        this.channels.remove(channel);
    }

    public void setNews(String news) {
        this.news = news;
        for (Channel channel : this.channels) {
            channel.update(this.news);
        }
    }
}

NewsAgency является наблюдаемым, и когдаnews обновляется, состояниеNewsAgency изменяется. Когда происходит изменение,NewsAgency уведомляет наблюдателей об этом факте, вызывая их методupdate().

To be able to do that, the observable object needs to keep references to the observers, а в нашем случае это переменнаяchannels.

Давайте теперь посмотрим, как может выглядеть наблюдатель, классаChannel. Он должен иметь методupdate(), который вызывается при изменении состоянияNewsAgency:

public class NewsChannel implements Channel {
    private String news;

    @Override
    public void update(Object news) {
        this.setNews((String) news);
    }
}

ИнтерфейсChannel имеет только один метод:

public interface Channel {
    public void update(Object o);
}

Теперь, если мы добавим экземплярNewsChannel в список наблюдателей, и изменим состояниеNewsAgency, экземплярNewsChannel будет обновлен:

NewsAgency observable = new NewsAgency();
NewsChannel observer = new NewsChannel();

observable.addObserver(observer);
observable.setNews("news");
assertEquals(observer.getNews(), "news");

В базовых библиотеках Java есть предопределенный интерфейсObserver, который еще больше упрощает реализацию шаблона наблюдателя. Давайте посмотрим на это.

3. Реализация сObserver

Интерфейсjava.util.Observer определяет методupdate(), поэтому нет необходимости определять его самостоятельно, как мы делали в предыдущем разделе.

Давайте посмотрим, как мы можем использовать это в нашей реализации:

public class ONewsChannel implements Observer {

    private String news;

    @Override
    public void update(Observable o, Object news) {
        this.setNews((String) news);
    }
}

Здесь второй аргумент исходит отObservable, как мы увидим ниже.

Чтобы определить наблюдаемый,, нам нужно расширить класс JavaObservable:

public class ONewsAgency extends Observable {
    private String news;

    public void setNews(String news) {
        this.news = news;
        setChanged();
        notifyObservers(news);
    }
}

Обратите внимание, что нам не нужно напрямую вызывать метод наблюдателяupdate(). Мы просто вызываемstateChanged() иnotifyObservers(), а классObservable делает все остальное за нас.

Кроме того, он содержит список наблюдателей и предоставляет методы для поддержки этого списка -addObserver() иdeleteObserver()..

Чтобы проверить результат, нам просто нужно добавить наблюдателя в этот список и установить новости:

ONewsAgency observable = new ONewsAgency();
ONewsChannel observer = new ONewsChannel();

observable.addObserver(observer);
observable.setNews("news");
assertEquals(observer.getNews(), "news");

ИнтерфейсObserver несовершенен и устарел с Java 9. Одним из его недостатков является то, чтоObservable - это не интерфейс, а класс, поэтому подклассы не могут использоваться как наблюдаемые.

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

Давайте посмотрим на интерфейсProperyChangeListener, который рекомендуется использовать вместоObserver.

4. Реализация сPropertyChangeListener

In this implementation, an observable must keep a reference to the PropertyChangeSupport instance. Помогает отправлять уведомления наблюдателям при изменении свойства класса.

Давайте определим наблюдаемое:

public class PCLNewsAgency {
    private String news;

    private PropertyChangeSupport support;

    public PCLNewsAgency() {
        support = new PropertyChangeSupport(this);
    }

    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        support.addPropertyChangeListener(pcl);
    }

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

    public void setNews(String value) {
        support.firePropertyChange("news", this.news, value);
        this.news = value;
    }
}

Используя этотsupport, мы можем добавлять и удалять наблюдателей и уведомлять их, когда состояние наблюдаемых изменений:

support.firePropertyChange("news", this.news, value);

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

Наблюдатели должны реализоватьPropertyChangeListener:

public class PCLNewsChannel implements PropertyChangeListener {

    private String news;

    public void propertyChange(PropertyChangeEvent evt) {
        this.setNews((String) evt.getNewValue());
    }
}

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

Давайте протестируем реализацию, чтобы убедиться, что она также работает:

PCLNewsAgency observable = new PCLNewsAgency();
PCLNewsChannel observer = new PCLNewsChannel();

observable.addPropertyChangeListener(observer);
observable.setNews("news");

assertEquals(observer.getNews(), "news");

5. Заключение

В этой статье мы рассмотрели два способа реализации шаблона проектированияObserver в Java, причем предпочтение отдается подходуPropertyChangeListener.

Исходный код статьи доступенover on GitHub.