За время существования компьютерных наук и программирования искусные программисты обнаружили повторяющиеся сходства в проблемах, которые они пытались решить. Используя эти сходства, были разработаны различные шаблоны проектирования, которые могут решать конкретные проблемы, которые могут возникнуть. Шаблон проектирования Observer — один из таких случаев.

Что такое шаблон проектирования Observer?

Шаблон проектирования наблюдателя состоит из двух основных компонентов: субъекта и наблюдателя. В нем описывается взаимосвязь между этими двумя компонентами, включая действия, которые они могут выполнять. Как правило, субъекты будут ждать, пока не произойдет событие или не будут достигнуты определенные критерии, а затем они уведомят наблюдателей, которым необходимо знать эту информацию (гуру рефакторинга) (Packt). Ниже приведены два грубо нарисованных изображения субъекта и наблюдателя, которые я буду использовать позже в примере.

Предмет

У субъекта есть две основные задачи: обработка запросов от наблюдателей и уведомление соответствующих наблюдателей, когда происходит событие или достигаются определенные критерии. Два типа запросов, которые он обрабатывает от наблюдателей, — это запросы на подписку и отмену подписки. Когда субъект получает запрос на подписку от наблюдателя, он добавляет этого наблюдателя в список подписанных наблюдателей. Если субъект получает запрос на отмену подписки от наблюдателя, он удалит этого наблюдателя из списка (гуру рефакторинга). Как только событие субъекта произойдет или будут достигнуты его критерии, он уведомит всех подписанных наблюдателей в своем списке (Packt). Ниже я написал некоторый код JavaScript для простой темы, используя онлайн-ресурс, чтобы напомнить себе о правильном синтаксисе для определения классов в JavaScript (W3 Schools). В следующем разделе у меня есть аналогичный фрагмент кода для наблюдателя, который может быть полезен для понимания поведения кода субъекта.

class Subject {
    //The constructor sets up the object's data when initialized
    constructor(name) {
        this.name = name;
        this.observers = new Set();
    }

    //notify() informs all interested observers of the subject
    //when the event has occured
    notify() {
        this.observers.forEach((observer) => {
            console.log(`${this.name} is notifying ${observer.name}`);
            setTimeout(() => {
                observer.handleNotify(this);
              }, 0);
            
        })
    }

    //handleSubscribe() handles subscribe requests from observers
    handleSubscribe(observer) {
        this.observers.add(observer);
    }

    //handleSubscribe() handles unsubscribe requests from observers
    handleUnsubscribe(observer) {
        this.observers.delete(observer);
    }
}

Наблюдатель

Наблюдатель также выполняет две основные задачи: отправляет запросы субъектам и обрабатывает уведомления от субъектов. Два типа запросов, которые наблюдатели отправляют субъектам, — это запросы на подписку и отмену подписки, которые говорят субъекту добавить наблюдателя в свой подписанный список или удалить его из списка соответственно (гуру рефакторинга). Затем, когда наблюдатель получает уведомления от субъекта, он обрабатывает уведомление, выполняя действие (Packt). Ниже я написал некоторый код JavaScript для простого наблюдателя, снова используя онлайн-справочник по синтаксису (W3 Schools). Этот фрагмент кода работает вместе с приведенным выше фрагментом кода темы для реализации полного шаблона проектирования наблюдателя.

class Observer {
    //The constructor sets up the object's data when initialized
    constructor(name) {
        this.name = name;
    }

    //subscribe() lets a subject know that the observer is interested
    subscribe(subject) {
        subject.handleSubscribe(this);
        console.log(`I (${this.name}) have subscribed to ${subject.name}!`);
    }

    //unsubscribe() lets a subject know that the observer is no longer interested
    unsubscribe(subject) {
        subject.handleUnsubscribe(this);
        console.log(`I (${this.name}) have unsubscribed from ${subject.name}!`);
    }

    //handleNotify() handles what the Observer wants to do when notfied
    handleNotify(subject) {
        console.log(`I (${this.name}) have been notified by ${subject.name}!`);
    }
}

Взаимодействие субъектов и наблюдателей

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

Для тех из вас, кто предпочитает код, то же самое взаимодействие описано в приведенном ниже фрагменте кода. Я прокомментировал эффект каждой строки под ней.

const eyeball = new Observer('eyeball');
const star = new Subject('star');

star.notify();
//Nothing Happens (star has no subscribed observers)

eyeball.subscribe(star);
//eyeball will be added to star's list of subscribed observers

star.notify();
//star will notify eyeball, 
//since eyeball is on its list of subscribed observers

eyeball.unsubscribe(star);
//eyeball will be removed from star's list of subscribed observers

star.notify();
//Nothing Happens (star has no subscribed observers)

Почему этот шаблон проектирования актуален?

Этот шаблон проектирования актуален, потому что он очень часто проявляется в реактивной и адаптивной разработке пользовательского интерфейса (включая веб-дизайн). Это связано с тем, что реактивные и отзывчивые пользовательские интерфейсы часто требуют выполнения действий после того, как произойдет определенное событие или будут достигнуты определенные критерии. Например, если веб-разработчик хочет, чтобы веб-страница выполняла действие при нажатии на ‹div›, он может написать некоторый код, подобный фрагменту ниже. Функция addEventListener() создает прослушиватель событий (наблюдатель), который выполняет определенное действие, когда обработчик событий ‹div› (субъект) уведомляет его о том, что произошел щелчок (конкретное событие) (Dofactory) (Packt).

document.querySelector('#someDiv').addEventListener(click, () => {/* Do Something */})

Другим примером шаблона проектирования наблюдателя в действии является хук useState в React. Хук useState создает объект состояния (субъект), который ожидает изменения своего значения (конкретное событие), чтобы сообщить средству визуализации React, что состояние изменилось, чтобы он мог повторно отображать страницу (Фернандес).

const [value, setValue] = useState('initial value');

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

Библиография

Фабрика. (н.д.). Jобозреватель avaScript. Фабрика. Получено 31 марта 2023 г. с https://www.dofactory.com/javascript/design-patterns/observer.

Фернандес, Г. (2012 г., 1 декабря). Крюки React и паттерн Наблюдатель. Простой английский. Получено 31 марта 2023 г. с https://plainenglish.io/blog/react-hooks-and-the-observer-pattern-1e4274f0e5f5.

пакет (н.д.). Шаблон наблюдателя. пакет Получено 31 марта 2023 г. с https://subscription.packtpub.com/book/web-development/9781783287314/1/ch01lvl1sec12/the-observer-pattern.

Гуру рефакторинга. (н.д.). Наблюдатель. Гуру рефакторинга. Получено 31 марта 2023 г. с https://refactoring.guru/design-patterns/observer.

Школы W3. (н.д.). Классы JavaScript. Школы W3. Получено 31 марта 2023 г. с https://www.w3schools.com/js/js_classes.asp.

Изображения, сделанные мной в MS Paint

Код, написанный мной в VS Code