Что это такое
Паттерн Observer позволяет одному объекту сообщать о событиях многим подписчикам, не зная, кто они. Подписчики регистрируются один раз; после этого каждое событие автоматически доходит до всех.
Издатель зависит только от небольшого интерфейса подписчика — новые виды слушателей появляются без единой изменённой строки в издателе.
Пример: одно оповещение — все подписчики
Монитор хранит массив подписчиков. Когда что-то происходит, он уведомляет весь массив — сегодня это оповещение по электронной почте и панель мониторинга; завтра — сообщение в чат, достаточно его подписать.
Опубликовать один раз, доставить всем
Один вызов alert(), две доставки: письмо на [email protected]: диск почти заполнен и панель показывает: диск почти заполнен. Монитор понятия не имеет, что такое письмо или панель, — он знает только про объекты, которые можно уведомить.
Вывод: программа на TypeScript и захваченный вывод
interface Subscriber {
notify(event: string): void
}
class EmailAlert implements Subscriber {
private address: string
constructor(address: string) {
this.address = address
}
notify(event: string): void {
console.log("письмо на", this.address + ":", event)
}
}
class Dashboard implements Subscriber {
notify(event: string): void {
console.log("панель показывает:", event)
}
}
// Издатель хранит список и уведомляет всех, кто в нём есть.
class Monitor {
private subscribers: Subscriber[] = []
subscribe(subscriber: Subscriber): void {
this.subscribers.push(subscriber)
}
alert(event: string): void {
for (const subscriber of this.subscribers) {
subscriber.notify(event)
}
}
}
const monitor = new Monitor()
monitor.subscribe(new EmailAlert("[email protected]"))
monitor.subscribe(new Dashboard())
monitor.alert("диск почти заполнен")
Когда его применять
Применяйте, когда на одни и те же события должны реагировать несколько частей программы и список реагирующих меняется — обновления интерфейса, оповещения, сброс кеша, ленты активности.
Не применяйте, когда получатель ровно один и он фиксирован — вызывайте его напрямую. И прежде чем писать такой список самому, посмотрите, что уже даёт платформа: в Node.js есть EventEmitter, а в браузерах — EventTarget; и то и другое — этот же паттерн, уже написанный и проверенный.
Плюсы и минусы
Плюсы: издатель и подписчики остаются слабо связанными; подписчики появляются и исчезают во время выполнения; новые реакции не требуют изменений в издателе.
Минусы: поток выполнения становится невидимым — ответ на вопрос «кто на это реагирует?» приходится искать; медленный или падающий подписчик может застопорить цикл уведомлений; забытые отписки приводят к утечкам.