Паттерн наблюдатель (Observer)

02.11.2019 1 Автор Jeff

Для чего он нужен?

Этот шаблон очень популярен потому, что очень часто возникает необходимость выстроить некую коммуникацию между объектами или модулями приложения. Вернее, если происходит какое-то событие – нужно чтобы о нем узнали все кто заинтересованны в его наступлении. Когда мы кликаем по кнопке, то происходит событие о котором узнает обработчик данного события и выполняет некоторые действия.

Пример из жизни:

Когда я в группе ВКонтакте размещаю сообщение, то о нем становится известно всем, кто подписан на группу. Тоже самое, если я размещаю новый сюжет на канале. Я, конечно, не смотрел исходники YouTube, но очень вероятно, что они использовали именно этот шаблон.

Пример:

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

Перечислю исходные данные:

public class Magazine {
    /**
     * Номер выпуска
     */
    private int issue = 0;
    /**
     * Считает количество изданных выпусков
     */
    private static int counter = 0;

    public Magazine() {
        this.issue = ++counter;
    }

    @Override
    public String toString() {
        return "Issue " + issue;
    }
}

public class Magazine {
    /**
     * Номер выпуска
     */
    private int issue = 0;
    /**
     * Считает количество изданных выпусков
     */
    private static int counter = 0;
 
    public Magazine() {
        this.issue = ++counter;
    }
 
    @Override
    public String toString() {
        return "Issue " + issue;
    }
}

Класс Magazine(журнал), содержит поле issue, которое хранит порядковый номер выпуска.

import java.util.List;

/**
 * Этот интерфейс ввел для удобства.
 * Чтобы не дублировать метод вывода
 * на экран для разных подписчиков.
 */
public interface Magazinable {
    /**
     * Возвращает все журналы которые получил объект
     *
     * @return - список журналов
     */
    List getMagazines();

    /**
     * Возвращает имя того кто получил журнал
     *
     * @return - имя получателя
     */
    String getName();
}

import java.util.List;
 
/**
 * Этот интерфейс ввел для удобства.
 * Чтобы не дублировать метод вывода
 * на экран для разных подписчиков.
 */
public interface Magazinable {
    /**
     * Возвращает все журналы которые получил объект
     *
     * @return - список журналов
     */
    List getMagazines();
 
    /**
     * Возвращает имя того кто получил журнал
     *
     * @return - имя получателя
     */
    String getName();
}

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

Интерфейс Magazinable нужен просто для удобного вывода на экран всех полученных журналов подписчика.

import java.util.ArrayList;
import java.util.List;

public class Person implements Magazinable {

    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;

    public Person(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }

    @Override
    public List getMagazines() {
        return magazines;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}


import java.util.ArrayList;
import java.util.List;
 
public class Person implements Magazinable {
 
    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;
 
    public Person(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }
 
    @Override
    public List getMagazines() {
        return magazines;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}

Класс Person – конкретный подписчик. Содержит имя и список полученных им журналов. Реализует метод для получения нового журнала.

public class Publisher {

    private List subscribers;
    private Magazine newMagazine;

    public Publisher() {
        this.subscribers = new ArrayList();
    }

    public void publishMagazine() {
        this.newMagazine = new Magazine();
        magazinePublished();
    }

    private void magazinePublished() {
        notifyAllSubscribers();
    }

    @Override
    public void notifyAllSubscribers() {
        for (Person person: subscribers) {
            subscriber.add(newMagazine);
        }
    }

    @Override
    public void subscribe(Person person) {
        subscribers.add(person);
    }

    @Override
    public void unsubscribe(Person person) {
        int index = subscribers.indexOf(person);
        if (index >= 0) {
            subscribers.remove(index);
        }
    }
}


public class Publisher {
 
    private List subscribers;
    private Magazine newMagazine;
 
    public Publisher() {
        this.subscribers = new ArrayList();
    }
 
    public void publishMagazine() {
        this.newMagazine = new Magazine();
        magazinePublished();
    }
 
    private void magazinePublished() {
        notifyAllSubscribers();
    }
 
    @Override
    public void notifyAllSubscribers() {
        for (Person person: subscribers) {
            subscriber.add(newMagazine);
        }
    }
 
    @Override
    public void subscribe(Person person) {
        subscribers.add(person);
    }
 
    @Override
    public void unsubscribe(Person person) {
        int index = subscribers.indexOf(person);
        if (index >= 0) {
            subscribers.remove(index);
        }
    }
}

Класс Publisher – это издатель, который рассылает журналы своим подписчикам. Также содержит методы для удаления и добавления подписчиков в список рассылки. И метод оповещения подписчиков о том, что вышел новый журнал.

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");

        Publisher publisher = new Publisher();

        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.publishMagazine();
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();

        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
    }

 /**
 * Выводит список журналов
 */
 public static void printMagazines(Magazinable magazinable) {
     System.out.println(magazinable.getName() + ": ");
     for (Magazine magazine : magazinable.getMagazines()) {
         System.out.println(magazine);
     }
     System.out.println("");
     }
}

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");
 
        Publisher publisher = new Publisher();
 
        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.publishMagazine();
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();
 
        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
    }
 
 /**
 * Выводит список журналов
 */
 public static void printMagazines(Magazinable magazinable) {
     System.out.println(magazinable.getName() + ": ");
     for (Magazine magazine : magazinable.getMagazines()) {
         System.out.println(magazine);
     }
     System.out.println("");
     }
}

Ну и метод main, который создает несколько подписчиков. Подписывает их на получения журналов от издателя. Рассылает журналы и выводит список полученных журналов у подписчиков.

Sasha:
Issue 1

Masha:

Pasha:
Issue 1
Issue 2


Process finished with exit code 0

Это результат выполнения кода. Как видно, Маша не оформляла подписку – в результате журналов у нее нет. Саша отписался после получения первого журнала – в результате у него только один журнал. А Паша – получил все журналы, потому что не отписывался.

Если это все, что от нас требует заказчик, то этот код максимально прост. Это идеальное решение.

Но проблема возникает тогда, когда нам говорят добавить возможность оформлять подписку еще и юридическим лицам. И мы начинаем переписывать наше приложение.

import java.util.ArrayList;
import java.util.List;

public class Company implements Magazinable {

    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;

    public Company(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }

    @Override
    public List getMagazines() {
        return magazines;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}


import java.util.ArrayList;
import java.util.List;
 
public class Company implements Magazinable {
 
    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;
 
    public Company(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }
 
    @Override
    public List getMagazines() {
        return magazines;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}

Добавили новый класс Company(компания). По своему содержимому, он идентичен классу Person. Также хранит имя компании + список полученных журналов.

Проблемы начинаются когда мы понимаем, что без модификации класса Publisher нам не обойтись. Во-первых, нам некуда деваться и нужно добавлять еще список компаний подписанных на рассылку, и инициализировать его в конструкторе. Затем в метод notifyAllSubscribers мы добавляем цикл для перебора подписанных юридических лиц. И в-третьих, добавляем еще по два метода для подписки и отписки новой сущности. В итоге мы жесточайше дублируем код:

public void subscribe(Person person) {
    subscribers.add(person);
}

public void subscribe(Company company) {
    companySubscribers.add(company);
}

public void unsubscribe(Person person) {
    int index = subscribers.indexOf(person);
    if (index >= 0) {
        subscribers.remove(index);
    }
}

public void unsubscribe(Company company) {
    int index = companySubscribers.indexOf(company);
    if (index >= 0) {
        companySubscribers.remove(index);
    }
}

public void subscribe(Person person) {
    subscribers.add(person);
}
 
public void subscribe(Company company) {
    companySubscribers.add(company);
}
 
public void unsubscribe(Person person) {
    int index = subscribers.indexOf(person);
    if (index >= 0) {
        subscribers.remove(index);
    }
}
 
public void unsubscribe(Company company) {
    int index = companySubscribers.indexOf(company);
    if (index >= 0) {
        companySubscribers.remove(index);
    }
}

Это плохо потому, что увеличивается количество кода, который нам придется поддерживать. Любой фикс или изменение будет требовать больше времени, а соответственно и денег.

 

В классе Main мы сделали достаточно допустимые изменения. От них никуда не деться.

Sasha:
Issue 1

Masha:

Pasha:
Issue 1
Issue 2

Google:
Issue 1

Process finished with exit code 0

В результате мы видим, что теперь есть возможность подписываться на рассылку и юридическим лицам.

Основная причина наших проблем с классом Publisher, это то, что мы не предусмотрели возможность изменения требований заказчика. Если бы мы сделали это, то могли бы выбрать другую архитектуру. Хорошим решением было бы сделать так, чтобы издатель ничего не знал о своих подписчиках, кроме какого-нибудь универсального интерфейса. Этого нам помог бы достичь паттерн .

Теперь давайте посмотрим на пример кода из параллельной вселенной, где мы изначально воспользовались данным шаблоном:

public class Magazine {
    /**
     * Номер выпуска
     */
    private int issue = 0;
    /**
     * Считает количество изданных выпусков
     */
    private static int counter = 0;

    public Magazine() {
        this.issue = ++counter;
    }

    @Override
    public String toString() {
        return "Issue " + issue;
    }
}


public class Magazine {
    /**
     * Номер выпуска
     */
    private int issue = 0;
    /**
     * Считает количество изданных выпусков
     */
    private static int counter = 0;
 
    public Magazine() {
        this.issue = ++counter;
    }
 
    @Override
    public String toString() {
        return "Issue " + issue;
    }
}
 

import java.util.List;

/**
 * Этот интерфейс ввел для удобства.
 * Чтобы не дублировать метод вывода
 * на экран для разных подписчиков.
 */
public interface Magazinable {
    /**
     * Возвращает все журналы которые получил объект
     *
     * @return - список журналов
     */
    List getMagazines();

    /**
     * Возвращает имя того кто получил журнал
     *
     * @return - имя получателя
     */
    String getName();
}


import java.util.List;
 
/**
 * Этот интерфейс ввел для удобства.
 * Чтобы не дублировать метод вывода
 * на экран для разных подписчиков.
 */
public interface Magazinable {
    /**
     * Возвращает все журналы которые получил объект
     *
     * @return - список журналов
     */
    List getMagazines();
 
    /**
     * Возвращает имя того кто получил журнал
     *
     * @return - имя получателя
     */
    String getName();
}

Классы Magazine и Magazinable ни как не изменились по сравнению с первым примером. Самые большие изменения, это два появившихся интерфейса:

/**
 * Интерфейс для подписчиков
 */
public interface Subscriber {
    /**
     * Передает журнал подписчику
     *
     * @param magazine - новый журнал
     */
    void add(Magazine magazine);
}

/**
 * Интерфейс для подписчиков
 */
public interface Subscriber {
    /**
     * Передает журнал подписчику
     *
     * @param magazine - новый журнал
     */
    void add(Magazine magazine);
}

Этот интерфейс должен реализовывать класс, который рассматривается в качестве подписчика. Метод add обновляет состояние подписчика в тот момент, когда редакция издает новый журнал.

/**
 * Интерфейс для работы с издательством
 */
public interface Subscribable {
    /**
     * Подписывает на рассылку
     *
     * @param subscriber - подписчик
     */
    void subscribe(Subscriber subscriber);

    /**
     * Отписывает от рассылки
     *
     * @param subscriber - подписчик
     */
    void unsubscribe(Subscriber subscriber);

    /**
     * Уведомляет подписчиков о обновления
     */
    void notifyAllSubscribers();
}

/**
 * Интерфейс для работы с издательством
 */
public interface Subscribable {
    /**
     * Подписывает на рассылку
     *
     * @param subscriber - подписчик
     */
    void subscribe(Subscriber subscriber);
 
    /**
     * Отписывает от рассылки
     *
     * @param subscriber - подписчик
     */
    void unsubscribe(Subscriber subscriber);
 
    /**
     * Уведомляет подписчиков о обновления
     */
    void notifyAllSubscribers();
}

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

import java.util.ArrayList;
import java.util.List;

public class Person implements Subscriber, Magazinable {

    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;

    public Person(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }

    @Override
    public List getMagazines() {
        return magazines;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}

import java.util.ArrayList;
import java.util.List;
 
public class Person implements Subscriber, Magazinable {
 
    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;
 
    public Person(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }
 
    @Override
    public List getMagazines() {
        return magazines;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}

От первого примера класс Person отличается только тем, что он теперь реализует интерфейс Subscriber. Внутри этого класса ничего не поменялось, потому что у него и так был метод add.

public class Publisher implements Subscribable {

    private List subscribers;
    private Magazine newMagazine;

    public Publisher() {
        this.subscribers = new ArrayList();
    }

    public void publishMagazine() {
        this.newMagazine = new Magazine();
        magazinePublished();
    }

    private void magazinePublished() {
        notifyAllSubscribers();
    }

    @Override
    public void notifyAllSubscribers() {
        for (Subscriber subscriber : subscribers) {
            subscriber.add(newMagazine);
        }
    }

    @Override
    public void subscribe(Subscriber subscriber) {
        subscribers.add(subscriber);
    }

    @Override
    public void unsubscribe(Subscriber subscriber) {
        int index = subscribers.indexOf(subscriber);
        if (index >= 0) {
            subscribers.remove(index);
        }
    }
}


public class Publisher implements Subscribable {
 
    private List subscribers;
    private Magazine newMagazine;
 
    public Publisher() {
        this.subscribers = new ArrayList();
    }
 
    public void publishMagazine() {
        this.newMagazine = new Magazine();
        magazinePublished();
    }
 
    private void magazinePublished() {
        notifyAllSubscribers();
    }
 
    @Override
    public void notifyAllSubscribers() {
        for (Subscriber subscriber : subscribers) {
            subscriber.add(newMagazine);
        }
    }
 
    @Override
    public void subscribe(Subscriber subscriber) {
        subscribers.add(subscriber);
    }
 
    @Override
    public void unsubscribe(Subscriber subscriber) {
        int index = subscribers.indexOf(subscriber);
        if (index >= 0) {
            subscribers.remove(index);
        }
    }
}

Данный Publisher тоже реализует новый интерфейс Subscribable. Его основное отличие от первого примера в том, что в данном интерфейсе больше не фигурирует класс Person. Мы абстрагировались от конкретных классов и работаем теперь только с интерфейсом Subscriber.

Это и называется “программировать на уровне интерфейсов”. Таким образом код становится менее связанным и чуть позже Вы увидите преимущества такого подхода.

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");

        Publisher publisher = new Publisher();

        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.publishMagazine();
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();

        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
    }

    /**
     * Выводит список журналов
     */
    public static void printMagazines(Magazinable magazinable) {
        System.out.println(magazinable.getName() + ": ");
        for (Magazine magazine : magazinable.getMagazines()) {
            System.out.println(magazine);
        }
        System.out.println("");
    }
}

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");
 
        Publisher publisher = new Publisher();
 
        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.publishMagazine();
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();
 
        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
    }
 
    /**
     * Выводит список журналов
     */
    public static void printMagazines(Magazinable magazinable) {
        System.out.println(magazinable.getName() + ": ");
        for (Magazine magazine : magazinable.getMagazines()) {
            System.out.println(magazine);
        }
        System.out.println("");
    }
}
Sasha:
Issue 1

Masha:

Pasha:
Issue 1
Issue 2


Process finished with exit code 0

Класс Main также ничем не отличается от первого примера, как и вывод на экран

public class Company implements Magazinable, Subscriber {

    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;

    public Company(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }

    @Override
    public List getMagazines() {
        return magazines;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}


public class Company implements Magazinable, Subscriber {
 
    private String name;
    /**
     * Список полученных журналов
     */
    private List magazines;
 
    public Company(String name) {
        this.name = name;
        this.magazines = new ArrayList();
    }
 
    @Override
    public List getMagazines() {
        return magazines;
    }
 
    @Override
    public String getName() {
        return name;
    }
 
    @Override
    public void add(Magazine magazine) {
        this.magazines.add(magazine);
    }
}

Выполняя новые требования заказчика мы видим преимущества архитектуры основанной на интерфейсах. Для того что бы добавить возможность подписываться юридическим лицам, нам достаточно просто создать класс Company. Единственное чем он отличие от первого примера в том, что он тоже реализует интерфейс Subsciber.

Нам не нужно менять класс Publisher. По тому что в нем используется абстракция Subscriber.

Повторюсь, мы добавили два интерфейса, первый это Subscribable, предназначенный для тех на кого подписываются, а второй – Subscriber, который нужен для тех, кто хочет подписаться на получение изменений.

Разница в коде

Sasha:
Issue 1

Masha:

Pasha:
Issue 1
Issue 2

Google:
Issue 1

Process finished with exit code 0

Вывод на экран не отличается от первого примера. Надеюсь, что нет смысла объяснять, что такой код более аккуратный и поддерживать его проще.

Теперь поговорим о теории данного шаблона.

Субъекты и наблюдатели в ООП

Обратите внимание на схему. Те классы на которые подписываются называются СУБЪЕКТАМИ а те которые подписываются называются НАБЛЮДАТЕЛЯМИ. В нашем примере субъектом был интерфейс Subscribable, а наблюдателями был интерфейс Subscriber. Соответственно их реализации были ConcreteSubject и ConcreteObserver

На схеме Subject может быть как интерфейсом так и классом. Вариант когда он является интерфейсом более гибкий, по крайней мере в java это так. Причина этому то, что в Java невозможно множественное наследование. Если Ваш класс Subject уже является чьим-то потомком, то это накладывает на Вас некоторые ограничения. Таких ограничений нет, если Вы работаете с интерфейсом.

Observer так же может быть классом или интерфейсом, с теми же последствиями.

Необязательно очень точно следовать UML схеме шаблон. Мы ведь хотим получить его преимущества, а не скопировать схему в коде. Я не оставлял ссылку на subject в конкретных реализациях наблюдателя, как это сделано на схеме. Смотрите пунктир от ConcreteObserver. И не создавал метод get/setState в ConcreteSubject.

Помните, что субъект ничего не должен знать про реализацию наблюдателей.

Определение

Паттерн наблюдатель определяет отношение “один-ко-многим” между объектами.

Один субъект – много наблюдателей.

Таким образом, что при изменении состояния одного объекта происходит автоматическое оповещение и обновление всех зависимых объектов.

Зависимые объекты это наблюдатели.

Плюсы и минусы

Единственное, что знает субъект о наблюдателе это то, что он реализует интерфейс Observer. Это позволяет нам добавлять новые типы наблюдателей без модификации субъекта.
Новые наблюдатели могут подписываться и отписываться от получения обновлений в любой момент.
Субъект и наблюдатель могут использоваться повторно.
Изменение одного никак не влияет другого.
Дополню вышесказанное еще тем, что работа кода не должна зависеть от порядка оповещения наблюдателей.

В языке Java существует встроенная поддержка данного паттерна

Это класс java.util.Observable, его расширяют субъекты. Я уже говорил про ограничения, которые накладывает на нас наследование в реализации через класс. Еще есть java.util.Observer, это интерфейс для наблюдателей.

Давайте наш последний пример переделаем, чтобы он использовал данные класс и интерфейс.

Первое что мы сделаем, это удалим наши интерфейсы.

Удалим интерфейсы

Удалим интерфейс 1

Классы Company и Person теперь реализуют интерфейс Observer. Метод update вторым параметром получает ссылку на объект журнал.

Интерфейс Observer имплементирован

Класс publisher больше не отвечает за подписку и отписку. Все эти методы находятся в его предке. Это хорошая новость, т.к. нам теперь не нужно их реализовывать самостоятельно. Тем более, что эти методы синхронизованы. Для того чтобы все заработало, нужно еще помнить про метод setChanged, который переключает состояние класса в режим “изменен”. Если этого не сделать, то наблюдатели не получат обновления.

Класс Main

Класс Main теперь выглядит так. Обратите внимание на то, как теперь называются методы для работы с подписками.

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");
        Company google = new Company("Google");

        Publisher publisher = new Publisher();

        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.subscribe(google);
        publisher.publishMagazine();
        publisher.unsubscribe(google);
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();

        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
        printMagazines(google);
    }

    /**
     * Выводит список журналов
     */
    public static void printMagazines(Magazinable magazinable) {
        System.out.println(magazinable.getName() + ": ");
        for (Magazine magazine : magazinable.getMagazines()) {
            System.out.println(magazine);
        }
        System.out.println("");
    }
}

public class Main {
    public static void main(String args[]) {
        Person sasha = new Person("Sasha");
        Person masha = new Person("Masha");
        Person pasha = new Person("Pasha");
        Company google = new Company("Google");
 
        Publisher publisher = new Publisher();
 
        publisher.subscribe(sasha);
        publisher.subscribe(pasha);
        publisher.subscribe(google);
        publisher.publishMagazine();
        publisher.unsubscribe(google);
        publisher.unsubscribe(sasha);
        publisher.publishMagazine();
 
        printMagazines(sasha);
        printMagazines(masha);
        printMagazines(pasha);
        printMagazines(google);
    }
 
    /**
     * Выводит список журналов
     */
    public static void printMagazines(Magazinable magazinable) {
        System.out.println(magazinable.getName() + ": ");
        for (Magazine magazine : magazinable.getMagazines()) {
            System.out.println(magazine);
        }
        System.out.println("");
    }
}

Вывод на экран ничем не отличается от предыдущих примеров.

В JDK есть еще несколько подобных классов и интерфейсов. Например, класс java.beans.PropertyChangeSupport и интерфейс java.beans.PropertyChangeListener

Если не ленитесь, посмотрите сами.

Еще один тип наблюдателей

Еще один тип наблюдателей

Если бы наша редакция выпускала еще и газеты, то мы могли бы реализовать шаблон немного по другому. Чтобы придать больше гибкости нашим классам. Наблюдатели могут получать либо газету, либо журнал. Просто они для этого должны сделать запрос через соответствующий getter.

Getter

getter_1

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

Заключение

Все!

Напишите в комментариях про какой следующий шаблон Вы хотите услышать. Или может Вас интересует что-то другое? Короче пишите в комментарии. Если ленитесь или стесняетесь писать сами, то пролайкайте комментарии других. По количеству лайков я определю какой вопрос победил и сниму сюжет на эту тему.

Читайте книги, пока!