Перейти к основному содержимому

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

· 4 мин. чтения

1. Обзор

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

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

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

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

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

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

public class NewsAgency {
private String news;
private List<Channel> 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 является наблюдаемым, и когда новости обновляются, состояние NewsAgency изменяется. Когда происходит изменение, NewsAgency уведомляет наблюдателей об этом факте, вызывая их метод update() .

Чтобы сделать это, наблюдаемый объект должен хранить ссылки на наблюдателей , и в нашем случае это переменная канала .

Давайте теперь посмотрим, как может выглядеть наблюдатель , класс 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 , который еще больше упрощает реализацию шаблона Observer. Давайте посмотрим на это.

3. Реализация с наблюдателем

Интерфейс 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 , как мы увидим ниже.

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

public class ONewsAgency extends Observable {
private String news;

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

Обратите внимание, что нам не нужно напрямую вызывать метод update() наблюдателя. Мы просто вызываем setChanged() и 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

В этой реализации наблюдаемый должен хранить ссылку на экземпляр PropertyChangeSupport . Это помогает отправлять уведомления наблюдателям при изменении свойства класса.

Определим наблюдаемую:

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.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 .

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