Вступление
В этой статье мы реализуем шаблон проектирования Observer для решения часто встречающейся проблемы при разработке объектно-ориентированного программного обеспечения.
Шаблоны проектирования - это стандартизированные решения общих проблем в индустрии разработки программного обеспечения. Зная их, разработчик может распознать, где каждый из них должен быть реализован и как он поможет решить конкретную проблему дизайна.
Предотвращение катастроф на раннем этапе разработки может сэкономить огромное количество времени и средств для команды, пытающейся выпустить продукт.
Шаблоны поведенческого дизайна
Шаблоны поведенческого проектирования обеспечивают распределение ответственности между экземплярами классов. Также они определяют типы отношений и связи между объектами.
Основная идея - добиться некоторого ожидаемого поведения приложения и в то же время создать гибкий дизайн.
Шаблон проектирования наблюдателя
Шаблон проектирования наблюдателя - это способ разработки подсистемы, которая позволяет многим объектам автоматически реагировать на изменения конкретного объекта, за которым "наблюдают".
Он обращается к декомпозиции Observable
и Observer
s - или издателя
и подписчиков.
Для Observable
мы используем термин Subject . Объекты, подписанные
на изменения Субъекта, называются Наблюдателями . Субъект и
наблюдатели обычно находятся в зависимости «один ко многим».
Шаблон проектирования наблюдателя также известен как шаблон подписчика события или шаблон слушателя.
Примечание. Java имеет официальную реализацию паттерна проектирования наблюдателя и является основой JMS (Java Message Service). Обычно он используется для создания приложений с четным управлением, хотя официальная реализация на самом деле не так широко распространена, и многие люди реализуют шаблон в соответствии со своими собственными сценариями использования.
Мотивация
Вероятно, наиболее известным примером является прослушиватель кнопки, который выполняет действие при нажатии кнопки. Этот шаблон, в общем, довольно распространен в компонентах графического интерфейса Java. Это способ реагировать на события, происходящие с визуальными объектами.
Как пользователь социальных сетей вы можете подписаться на некоторых людей. Можно сказать , что вы являетесь наблюдателем своего друга социальной подачи носителя (Subject наблюдения) , и вы получите уведомление о своих новых сообщениях и жизненных событиях. Интересно, что ваш друг также является наблюдателем вашей ленты.
Давайте добавим больше сложности и скажем, что у вас, вероятно, есть несколько или даже сотни разных наблюдателей, и они могут по-разному реагировать на ваши сообщения. Не исключено, что один объект может быть объектом наблюдения и наблюдателем другого субъекта . Они могут даже иметь эти отношения между собой.
В качестве более реального примера - пожарная сигнализация в торговом центре должна уведомить все магазины о возгорании. Эти магазины следят за сигналом пожарной тревоги и реагируют на его изменение.
Как видите, проблема довольно распространена, и зачастую ее нетривиально решить с помощью других проектов.
Выполнение
Предположим, что сеть магазинов хочет уведомить своих постоянных клиентов о продолжающейся продаже. Система отправит короткое сообщение всем подписавшимся клиентам всякий раз, когда будет активирована продажа.
В этом случае наш магазин является предметом наблюдения, а наши
покупатели наблюдают за ним. Давайте определим Subject
и Observer
для реализации нашими объектами:
public interface Subject {
public void addSubscriber(Observer observer);
public void removeSubscriber(Observer observer);
public void notifySubscribers();
}
Интерфейс Subject
довольно прост. Он предоставляет методы для
добавления и удаления подписчиков / наблюдателей и уведомления их об
изменении.
Интерфейс Observer
еще проще:
public interface Observer {
public void update(String message);
}
Единственное, что Observer
- это знать, когда есть обновления по его
теме. Их поведение, основанное на этом обновлении, будет отличаться от
класса к классу.
Разобравшись с нашими интерфейсами, давайте реализуем Subject
через
хранилище:
public class Store implements Subject {
private List<Observer> customers = new ArrayList<>();
@Override
public void addSubscriber(Observer customer) {
customers.add(customer);
}
@Override
public void removeSubscriber(Observer customer) {
customers.remove(customer);
}
@Override
public void notifySubscribers() {
System.out.println("A new item is on sale! Act fast before it sells out!");
for(Observer customer: customers) {
customer.update("Sale!");
}
}
}
Магазин содержит список наблюдателей (покупателей) и реализует методы добавления и удаления покупателей из списка.
Метод notifySubscribers()
просто просматривает их список и отправляет
им обновление.
У нас может быть столько Observer
сколько захотим. Совершенно
естественно, что люди иначе реагируют на продажу. Шопоголик наверняка
обрадуется, в то время как пассивный покупатель заметит сделку и
запомнит ее на потом.
Давайте продолжим и реализуем эти два типа клиентов:
public class ShopaholicCustomer implements Observer {
@Override
public void update(String message) {
processMessage(message);
}
private void processMessage(String message) {
System.out.println("Shopaholic customer is interested in buying the product on sale!");
// A complex psychologic response to a sale by a shopaholic
}
}
public class PassiveCustomer implements Observer {
@Override
public void update(String message) {
System.out.println("Passive customer made note of the sale.");
// Passive customer does not react to the message too much
}
}
И, наконец, давайте посмотрим на шаблон проектирования Observer в действии, активировав распродажу в магазине, за которым наблюдают несколько покупателей:
public static void main(String[] args) {
// Initialization
Subject fashionChainStores = new ChainStores();
Observer customer1 = new PassiveCustomer();
Observer customer2 = new ShopaholicCustomer();
Observer customer3 = new ShopaholicCustomer();
// Adding two customers to the newsletter
fashionChainStores.addSubscriber(customer1);
fashionChainStores.addSubscriber(customer2);
// Notifying customers (observers)
fashionChainStores.notifySubscribers();
// A customer has decided not to continue following the newsletter
fashionChainStores.removeSubscriber(customer1);
// customer2 told customer3 that a sale is going on
fashionChainStores.addSubscriber(customer3);
// Notifying the updated list of customers
fashionChainStores.notifySubscribers();
}
И запуск этого фрагмента кода даст:
A new item is on sale! Act fast before it sells out!
Passive customer made note of the sale.
Shopaholic customer is interested in buying the product on sale!
A new item is on sale! Act fast before it sells out!
Shopaholic customer is interested in buying the product on sale!
Shopaholic customer is interested in buying the product on sale!
Изменение состояния магазина приводит к изменению состояния подписанных клиентов. Тот же принцип применим к пожарной тревоге или новостной ленте. Как только кто-то публикует сообщение, все наблюдатели получают уведомление и предпринимают определенные действия в зависимости от их ответственности / интересов.
Мы можем изменить список наблюдателей за объектом в любой момент. Также
мы можем добавить любую реализацию интерфейса Observer
Это дает нам
возможность построить надежную управляемую событиями систему, которая
передает обновления наблюдателям и обновляет всю систему на основе
изменений в отдельном конкретном объекте.
Плюсы и минусы
Шаблон проектирования Observer - отличный вклад в поддержку принципа проектирования «открыть / закрыть» . Это помогает нам создавать проекты с высокой степенью сплоченности, но слабой связью.
Другими словами, у Наблюдателя и Субъекта есть строго определенная миссия. Субъект обновляет Наблюдателя некоторой информацией и не знает о реализации Наблюдателя. Эта характеристика дает нам гибкость.
Этот шаблон позволяет нам добавлять и удалять наблюдателей в любое время. Для этого не нужно изменять ни Subject, ни Observer.
Однако в шаблоне проектирования наблюдателя есть проблема.
Порядок уведомлений не находится под нашим контролем. При уведомлении нет приоритета среди подписчиков.
Это означает, что если выполнение Observer заранее зависит от выполнения другого Observer'а, нет гарантии, что эти два будут выполняться в этом порядке.
Однако важно понимать, что шаблон - это высокоуровневое описание конкретного решения. Когда мы применяем шаблон к двум разным приложениям, наш код будет другим. Мы можем отсортировать наших наблюдателей, и они получат уведомление в ожидаемом порядке. Это не особенность паттерна проектирования наблюдателя, но это то, что мы можем сделать.
Заключение
Когда вы знаете шаблоны проектирования, некоторые сложные проблемы можно свести к проверенным простым решениям.
Шаблон проектирования наблюдателя действительно полезен в системах, управляемых событиями, где многие объекты могут зависеть от состояния другого объекта. В случае плохой реализации это приведет к созданию связанного и жесткого приложения, в котором действительно сложно тестировать объекты по отдельности, а обновление кода станет проблемой.
В этой статье мы изучили, как решить эту проблему и создать гибкое независимое решение, за которое ваша команда будет благодарна.