Патерн спостерігач
Observer — це поведінковий патерн проектування, який дозволяє об'єктам підписуватися на події, що відбуваються в іншому об'єкті, та автоматично отримувати сповіщення про зміни в його стані. Об'єкт, що надсилає повідомлення, називається суб'єктом, а об'єкти, що підписуються на його сповіщення — спостерігачами.
Ідея патерну:
"Observer" спрощує координацію та підтримання узгодженості між кількома об'єктами, дозволяючи одному об'єкту (Subject) сповіщати інших (Observers) про свої зміни без жорсткого зв'язування з їх реалізацією.
Коли застосовувати "Observer"?
1. Потрібен зворотний зв'язок (callbacks)
Коли один об'єкт повинен інформувати інших про свої зміни, але не повинен знати деталей їх реалізації (принцип низької зв'язності)
2. Багато залежностей
Коли є кілька залежних об'єктів (спостерігачів), які повинні реагувати на зміни в одному об'єкті (суб'єкті)
3. Динамічна взаємодія
Коли кількість спостерігачів не є фіксованою під час компіляції і повинна змінюватися "на льоту".
Як працює "Observer"?
- Інтерфейс Subject
Описує методи для підписки (attach) та відписки (detach) спостерігачів, а також для сповіщення (notify). 2. Інтерфейс Observer
Містить метод update(), який викликається суб'єктом.
3. Конкретний суб'єкт
Зберігає стан, який спостерігачі можуть відстежувати, та реалізує методи підписки/відписки. Коли стан змінюється, викликає notify().
4. Конкретний спостерігач
Реагує на сповіщення, отримуючи необхідні дані від суб'єкта.
Приклад (TypeScript)
// 1. Інтерфейс Observer
interface Observer {
update(subject: Subject): void;
}
// 2. Інтерфейс Subject
interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(): void;
}
// 3. Конкретний суб'єкт
class ConcreteSubject implements Subject {
private observers: Observer[] = [];
private state: number = 0;
public attach(observer: Observer): void {
this.observers.push(observer);
}
public detach(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
public notify(): void {
for (const observer of this.observers) {
observer.update(this);
}
}
// Приклад зміни стану
public setState(value: number) {
this.state = value;
console.log(`Subject: state changed to ${this.state}`);
this.notify();
}
public getState(): number {
return this.state;
}
}
// 4. Конкретні спостерігачі
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() < 5) {
console.log("ConcreteObserverA reacts, as state < 5");
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() >= 5) {
console.log("ConcreteObserverB reacts, as state >= 5");
}
}
}
// Приклад використання
function clientCode() {
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();
subject.attach(observerA);
subject.attach(observerB);
// Зміна стану
subject.setState(3);
subject.setState(7);
subject.detach(observerA);
subject.setState(2); // Тепер лише observerB відстежує
}
clientCode();- ConcreteSubject зберігає стан і сповіщає всіх підписників, коли він змінюється.
- ConcreteObserverA та ConcreteObserverB реагують на зміни по-своєму, опитуючи суб'єкт.
Переваги
- Низька зв'язність: Суб'єкт не знає деталей спостерігачів, лише викликає їх метод update().
- Гнучкість: Можна динамічно додавати, видаляти та перемикати спостерігачів.
- Легкість в розширенні: Нові типи спостерігачів додаються без зміни суб'єкта.
Недоліки
- Каскад сповіщень: Помилка або надмірне сповіщення можуть призвести до "ланцюгової реакції" та ускладнити налагодження.
- Порядок сповіщень: Якщо різні спостерігачі мають залежності один від одного, порядок виклику update() може мати значення.
Важливо: Патерн Observer може бути потенційно небезпечним при великій кількості підписників або складних залежностях. Уважно відстежуйте, які події надсилаються і куди, щоб уникнути перетворення проекту на "спагетті" код.
Коли використовувати
- Коли потрібно централізовано обробляти події та сповіщати кілька зацікавлених об'єктів.
- Коли потрібно оновлювати об'єкти, коли один джерело змінюється без прямої залежності (наприклад, система підписки/сповіщення).
- Коли важливо, щоб нові модулі могли підписуватися на події без зміни вже існуючого коду.
Джерело
Патерн Observer спрощує реалізацію реактивних додатків і зменшує зв'язність між компонентми. Він широко використовується на фронтенді (наприклад, в архітектурі Flux/Redux, де підписники реагують на зміни стану сховища). Однак будьте обережні з потенційним зростанням складності при великій кількості підписників та перетинаючихся подій.
Читати більше про Observer тут.
- refactoring.guru - патерни та рефакторинг
- patterns.dev - патерни
Зміст
Коли застосовувати "Observer"? Як працює "Observer"? Приклад (TypeScript) Переваги Недоліки Коли використовувати Джерело
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.