Как пишет банда четырех (имеется ввиду книга «Паттерны объектно-ориентированного проектирования» 4 первоклассных разработчиков) назначение этого паттерна в том, чтобы определять зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Еще этот паттерн называют:
Dependents (подчиненные) или Publish-Subscribe (издатель — подписчик).
Но давайте попробуем разобраться на примере католической церкви :)
В ней имеются последователи, верящие в учение данной церкви. При появлении каких-либо новых догматов (обязательных вероучений) и не только - эти люди должны знать о них.
Но как же это можно было описать языком программирования, используя данный паттерн?
1. У нас есть «голос церкви» (сама церковь или папа Римский когда вещает ex cathedra), т. е. некий вещатель или субъект, оглашающий новости в церкви.
2. Есть прихожане этой церкви, т. е. некие наблюдатели, которые хотят быть в курсе важных событий.
Соответственно сегодня прихожан может быть 1,3 млд человек, а завтра больше или меньше.
И оповещать нужно только тех, кто находится в этой церкви (не нужно беспокоить атеистов лишний раз :).
Таким образом это все можно было бы выразить следующим образом:
Есть церковь, которая будет вещать своей пастве о чем-либо, в которой можно зарегистрироваться или наоборот выйти из нее:
public interface Church {
void registerParishioner(Parishioner parishioner);
void removeParishioner(Parishioner parishioner);
void notifyParishioners();
}
Есть конкретно католическая церковь c реализацией этих методов, а также новостями и списком людей, которым эти новости нужно транслировать:
public class CatholicChurch implements Church {
private List<parishioner> parishioners;
private String newsChurch;
public CatholicChurch() {
parishioners = new ArrayList<>();
}
public void setNewsChurch(String news) {
this.newsChurch = news;
notifyParishioners();
}
@Override
public void registerParishioner(Parishioner parishioner) {
parishioners.add(parishioner);
}
@Override
public void removeParishioner(Parishioner parishioner) {
parishioners.remove(parishioner);
}
@Override
public void notifyParishioners() {
for (Parishioner parishioner : parishioners)
parishioner.update(newsChurch);
}
}
Есть человек-прихожанин, который может войти в лоно церкви или выйти из него
(для упрощения кода мы позволим ему только войти :)
public class Parishioner {
private String name;
public Parishioner(String name, Church church) {
this.name = name;
church.registerParishioner(this);
}
void update(String newsChurch) {
System.out.println(name + "узнал новость: " + newsChurch);
}
}
Соответственно как это будет работать:
public class Main {
public static void main(String[] args) {
var catholicChurch = new CatholicChurch();
new Parishioner("Мартин Лютер", catholicChurch);
new Parishioner("Жан Кальвин", catholicChurch);
catholicChurch.setNewsChurch("Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года");
}
}
и результат работы программы:
Мартин Лютер узнал новость: Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года
Жан Кальвин узнал новость: Инквизиция была ошибкой... месса Mea Culpa 12 марта 2000 года
Т.е. как только в церкви появится новость, будут оповещены о ней все, кто находятся в массиве зарегистрированных членов данной церкви.
В чем недостатки данной реализации:
1. Во первых интерфейс где можно зарегистрироваться и получать новости может касаться не только данной церкви (возможно такое потребуется). Поэтому можно было бы сразу вынести это в отдельный интерфейс Observable.
2. Так же можно было бы поступить с прихожанами церкви, а именно метод update вынести в отдельный интерфейс и реализовать у нужного прихожанина его. Тогда данный метод смогут использовать вообще и не прихожане католической церкви, а например векующие в существование эльфов (имеется ввиду движение «Дорога к Единорогу»). Т.е. создать интерфейс Observer с методом update.
Что в итоге получится:
interface Observable {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
public class CatholicChurch implements Observable {
private List<observer> parishioners;
private String newsChurch;
public CatholicChurch() {
parishioners = new ArrayList<>();
}
public void setNewsChurch(String news) {
this.newsChurch = news;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
parishioners.add(o);
}
@Override
public void removeObserver(Observer o) {
parishioners.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : parishioners)
o.update(newsChurch);
}
}
interface Observer {
void update (String news);
}
public class Parishioner implements Observer {
private String name;
public Parishioner(String name, Observable o) {
this.name = name;
o.registerObserver(this);
}
@Override
public void update(String news) {
System.out.println(name + " узнал новость: " + news);
}
}
Таким образом:
Мы «ослабили связь» между церковью и прихожанами, что естественно хорошо только в программировании :) Субъект (католическая церковь) имеет лишь список слушателей (прихожан) и при получении новостей (изменений), транслирует данные новости своим слушателям. Можно завести теперь любой другой субъект (например протестанскую церковь) и там уже транслировать новости «своим» слушателям.
Также нужно учесть, что данные 2 класса (точнее класс Observable и интерфейс Observer) имелись в пакете java.util java, но сейчас они Deprecation с java 9 (https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Observable.html):
Deprecated.
This class and the Observer interface have been deprecated. The event model supported by Observer and Observable is quite limited, the order of notifications delivered by Observable is unspecified, and state changes are not in one-for-one correspondence with notifications. For a richer event model, consider using the java.beans package. For reliable and ordered messaging among threads, consider using one of the concurrent data structures in the java.util.concurrent package. For reactive streams style programming, see the Flow API.
Поэтому использовать их не нужно. А вместо них можно использовать другие, но суть паттерна от этого не изменится. Для примера давайте попробует использовать PropertyChangeListener (чтобы не писать лишние классы, которые уже написаны) из пакета java.beans.
Давайте посмотрим как это будет:
класс субъекта:
public class CatholicChurch {
private String news;
// используя support мы можем добавлять или удалять наших прихожан (слушателей)
private PropertyChangeSupport support;
public CatholicChurch() {
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;
}
}
и класс слушателя:
public class Parishioner implements PropertyChangeListener {
private String name;
public Parishioner(String name) {
this.name = name;
}
public void propertyChange(PropertyChangeEvent evt) {
this.setNews((String) evt.getNewValue());
}
public void setNews(String news) {
System.out.println(name + " узнал новость: " + news);
}
}
Если мы выполним следующий код:
public static void main(String[] args) {
CatholicChurch observable = new CatholicChurch();
observable.addPropertyChangeListener(new Parishioner("Мартин Лютер"));
observable.addPropertyChangeListener(new Parishioner("Жан Кальвин"));
observable.setNews("Дева Мария имеет непорочное зачатие... булла Ineffabilis Deus... 8 декабря 1854 года Папа Пий IX");
observable.setNews("Папа непогрешим... не всегда конечно, а только когда транслирует учение церкви ex cathedra... Первый Ватиканский собор 1869 год");
}
Получим следующий результат:
Мартин Лютер узнал новость: Дева Мария имеет непорочное зачатие... булла Ineffabilis Deus... 8 декабря 1854 года Папа Пий IX
Жан Кальвин узнал новость: Дева Мария имеет непорочное зачатие... булла Ineffabilis Deus... 8 декабря 1854 года Папа Пий IX
Мартин Лютер узнал новость: Папа непогрешим... не всегда конечно, а только когда транслирует учение церкви ex cathedra... Первый Ватиканский собор 1869 год
Жан Кальвин узнал новость: Папа непогрешим... не всегда конечно, а только когда транслирует учение церкви ex cathedra... Первый Ватиканский собор 1869 год
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
векующиев существование эльфов (имеется ввиду движение «Дорога к Единорогу») БУКВА "р" вместо "к" думаю подходит больше )))