наш блог
Разбор шаблона проектирования Observer на JavaScript

В очередной публикации разберем паттерн Observer. Еще один довольно простой, но вполне полезный шаблон проектирования на JavaScript. Он позволяет создавать объект Observable (Наблюдаемый), содержащий какие-либо данные. И объекты Observer (Наблюдатель), которые «следят» за изменениями в нем.
Изначально напишем класс Observable:
class Observable {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
broadcast() {
this.observers.forEach(observer => observer.notify());
}
}
У класса Observable есть обязательные методы subscribe, unsubscribe и broadcast. А также массив наблюдателей (подписчиков). Методы subscribe и unsubscribe подписывают и отписывают соответственно. Иначе говоря, добавляют или удаляют подписчиков в массив. А метод broadcast оповещает всех подписчиков о каких-либо событиях.
class Observer {
constructor(fn) {
this.fn = fn;
}
notify() {
this.fn();
}
}
Класс Observer крайне прост. При создании экземпляра он принимает в себя анонимную функцию, которую будет воспроизводить, если вызывается метод notify(). И сейчас станет понятно, зачем это нужно.
// Создадим наблюдателей
const Vasya = new Observer(() => { console.log('Вася') });
const Petya = new Observer(() => { console.log('Петя') });
const Kolya = new Observer(() => { console.log('Коля') });
// Создаем объект, за которым будем наблюдать
const observable = new Observable();
// Подписываем наших наблюдателей
observable.subscribe(Vasya);
observable.subscribe(Petya);
observable.subscribe(Kolya);
// Оповещаем подписчиков
observable.broadcast(); // Выведет в консоль Вася Петя Коля
// Отписываем Петю
observable.unsubscribe(Petya);
console.log('Петя отписан');
// Повторно оповещаем подписчиков, но уже без Пети
observable.broadcast(); // Вася Коля
Если все еще не понятно, как это можно применить на практике, то рассмотрим еще один пример. Расширим класс Observable.
class Observable {
constructor(state = {}) {
this.observers = []; // Список наблюдателей
this._state = state; // Состояние наблюдаемого объекта
}
// Изменить состояние
set state(state) {
this._state = {...this._state, ...state};
this.broadcast();
}
// Получить состояние
get state() {
return this._state;
}
// Подписать наблюдателя
subscribe(observer) {
this.observers.push(observer);
}
// Отписать наблюдателя
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// Уведомить всех наблюдателей
broadcast() {
this.observers.forEach(observer => observer.notify(this.state));
}
}
В класс добавилось состояние. По сути, это простой объект, изменение которого будет вызывать метод broadcast(). А в самом методе теперь будет отправляться состояние каждому подписчику. Следовательно, необходимо изменить класс Observer так, чтобы его метод notify() ожидал параметры.
class Observer {
constructor(fn) {
this.fn = fn;
}
notify(state) {
this.fn(state);
}
И теперь анонимная функция, которая передается в конструктор Observer будет содержать в параметрах состояние observable-объекта.
Итак, подключим фантазию. Представим, что Вася, Коля и Петя пришли в спортивный магазин, где оставили номера телефонов, на которые в будущем должны приходить уведомления о новых поступлениях.
const Vasya = new Observer((state) => { console.log('Вася', state) });
const Petya = new Observer((state) => { console.log('Петя', state) });
const Kolya = new Observer((state) => { console.log('Коля', state) });
const shop = new Observable();
shop.subscribe(Vasya);
shop.subscribe(Petya);
shop.subscribe(Kolya);
shop.state = {nike: 10}; // Изменение состояние вызовет метод broadcast, чем вызовет анонимные функции всех подписчиков и передаст им текущее состояние.
shop.unsubscribe(Petya);
console.log('Петя отписан');
console.log('Изменяем состояние');
shop.state = {adidas: 8, nike: 10}; // Уведомит всех, кроме Пети
Юрий Кизилов, компания Craft Group