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 .