JavaRush /Java блог /Java Developer /Шаблон Наблюдатель (Observer)

Шаблон Наблюдатель (Observer)

Статья из группы Java Developer
Как пишет банда четырех (имеется ввиду книга «Паттерны объектно-ориентированного проектирования» 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 год
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
BandanaClawaStudio Уровень 1
5 марта 2024
Эта статья задевает мои чувства атеиста.
Павел Перминов Уровень 51
21 сентября 2023
Извините за занудство, но поправьте код. private List<parishioner> parishioners; Дженерики указаны с маленькой буквы. В первом и во втором примере private List<observer> parishioners;
partiec Уровень 33
23 августа 2023
🙏аминь
Lyokha Blagodatskikh Уровень 48
16 января 2023
Пример божественнен!))))
Валерий Уровень 19
31 августа 2022
я тот всё. Там где устаревшее было еще можно было что-то понимать, но отдаленно. Когда речь пошла о новом, то я потерялся, understood nothing, поничтно ничего. Вообще нифига, ни капли, все понятные методы куда-то делись и появились какие-то левые интерфейсы, методы, названия, я ничего не понял и меня это бесит) потому что сюда пришел вообще с темы почему у меня не работает статичный интерфейс из фронт части написанной на Vaadin. Мне дали какие-то комментарии на ангилйском языке и я полез дальше и еще дальше и вот я уже на java rush читаю что такое observer. И нифига не понимаю и меня это бесит. Зачем так сложно все реализовано, что нифига не понятно.
kv0ut Уровень 51
17 августа 2022
Observable это класс а не интерфейс. При этом даже не абстрактный класс, у методов есть реализация
Андрей Уровень 39
6 марта 2022
Мало что понял в коде... Но принцип паттерна понятен. Нужно это все перенести в иде и разобраться, думаю будет понятно
Макс Дудин Уровень 41
16 октября 2021
http://en.wikipedia.org/wiki/Observer_pattern
Макс Дудин Уровень 41
30 июня 2021
Сначала всё было понятно...(там где Deprecated), а вот потом PropertyChangeSupport - вот это откуда ? все эти методы: addPropertyChangeListener removePropertyChangeListener this.setNews((String) evt.getNewValue());вот это зачем? может надо было PropertyChangeListener из пакета java.beans. тоже как-то показать, что там и как?
Hardy Уровень 32
17 июня 2021
Спасибо - отличная статья! Для понимания как работают наблюдатели- подписчики - слушатели . Аналогия с церковью Отличная :)