JavaRush /Java блог /Random UA /Шаблон Спостерігач (Observer)

Шаблон Спостерігач (Observer)

Стаття з групи Random UA
Як пише банда чотирьох (мається на увазі книга «Паттерни об'єктно-орієнтованого проектування» 4 першокласних розробників) призначення цього патерну в тому, щоб визначати залежність типу «один до багатьох» між об'єктами таким чином, що при зміні стану одного об'єкта всі, що залежать від нього, оповіщаються про це автоматично оновлюються. Ще цей патерн називають: Dependents (підлеглі) або Publish-Subscribe (видавець – передплатник). Але давайте спробуємо розібратися з прикладу католицької церкви :) У ній є послідовники, які вірять у вчення цієї церкви. З появою будь-яких нових догматів (обов'язкових віровчень) і не тільки - ці люди повинні знати про них. Але як це можна було описати мовою програмування, використовуючи даний патерн? 1. У нас є «голос церкви» (сама церква чи папа Римський коли мовить ex cathedra), тобто якийсь мовник чи суб'єкт, який оголошує новини в церкві. 2. Є парафіяни цієї церкви, т. е. деякі спостерігачі, які хочуть знати важливих подій. Відповідно сьогодні парафіян може бути 1,3 млд людей, а завтра більше чи менше. І сповіщати потрібно лише тих, хто перебуває в цій церкві (не потрібно турбувати атеїстів зайвий раз :). Таким чином це все можна було б виразити наступним чином: Є церква, яка буде мовити своїй пастві про щось, у якій можна зареєструватися або навпаки вийти з неї:
public interface Church {
    void registerParishioner(Parishioner parishioner);
    void removeParishioner(Parishioner parishioner);
    void notifyParishioners();
}
Є конкретно католицька церква з реалізацією цих методів, а також новинами та списком людей, яким ці новини потрібно транслювати:
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. Ця категорія і об'єкти керування мають бути неперевіреними. Цей model підтримується помічником і помічником є ​​тимчасово обмеженим, потік notifications, узгоджений по помічникам, не визначений, і зміни змін не є в одному-на-всього відповідності з поданнями. Для richer event model, consider using java.beans package. Для надійного і відповідного messaging серед threads, розглядає за допомогою однієї з поточних структур даних в java.util.concurrent package. Для реактивних streams style programming, viz 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 рік
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ