Коротка відповідь
middleObserver — це патерн, що дозволяє об’єктам (суб’єкту) повідомляти підписаним (наблюдникам) про зміни стану без жорсткої зв’язки. У TS це реалізується через інтерфейс `Subject` з методами `subscribe`, `unsubscribe` і `notify`; кожен `Observer` реалізує `update`. Наприклад: `subject.subscribe(observer); subject.notify();`
Повне пояснення
1. Що це і навіщо
Observer (Наблюдатель) – патерн поведінки, що розділяє залежність «один‑до‑багатьох» між об’єктами. Коли стан суб’єкта змінюється, всі підписані наблюдники отримують повідомлення. Це корисно в UI‑компонентах, event‑системах, pub/sub брокерах.
2. Принцип роботи / алгоритм
- Subject (суб’єкт) зберігає список
Observer‑ів. - Observer визначає інтерфейс
update(). - Підписка:
subject.subscribe(observer)додає до списку. - Випуск події:
subject.notify()ітерує список, викликаючиobserver.update(). - Відписка:
subject.unsubscribe(observer)видаляє з колекції.
3. Реалізація (JS / TS)
- Базова
interface Observer { update(data: any): void; }
class Subject {
private observers = new Set<Observer>();
subscribe(o: Observer) { this.observers.add(o); }
unsubscribe(o: Observer) { this.observers.delete(o); }
notify(data: any) { this.observers.forEach(o => o.update(data)); }
}
- Веб‑dev – реактивна модель Redux‑подібного store
class Store {
private state: any;
private listeners = new Set<() => void>();
getState() { return this.state; }
dispatch(action: any) { /* update state */ this.notify(); }
subscribe(fn: () => void) { this.listeners.add(fn); return () => this.listeners.delete(fn); }
private notify() { this.listeners.forEach(l => l()); }
}
- Новачок – простий таймер
class Timer {
private observers: ((t: number) => void)[] = [];
start() { let t=0; setInterval(()=>{t++; this.observers.forEach(f=>f(t));},1000); }
onTick(fn: (t:number)=>void) { this.observers.push(fn); }
}
4. Тестування (Jest або Vitest)
test('notify calls observers', () => {
const subject = new Subject();
const obs1 = { update: jest.fn() };
subject.subscribe(obs1);
subject.notify('data');
expect(obs1.update).toHaveBeenCalledWith('data');
});
5. Проблеми та edge cases
- Memory leaks – observers не видаляються, коли об’єкт знищується.
- Cyclic dependencies – Observer підписується на Subject, який в свою чергу залежить від Observer.
- Order of notifications – не гарантовано, що порядок викликів буде стабільним.
- Performance‑пастка – великі колекції observers збільшують час
notify. - Неправильне використання – підписка в конструкторі без відписки, що призводить до «зависання».
6. Оптимізація та продуктивність
- Використовувати
Setзамість масиву для уникнення дублювання. - Пакетна доставка (
batchNotify) замість окремих викликів. - Відписка під час
update(одноразові listeners).
7. Без JS / архітектурні альтернативи
- У HTML можна реалізувати через
CustomEventіdispatchEvent. - CSS‑система:
@mediaабоprefers-color-schemeсповіщає про зміни. - У чистій архітектурі без патерна можна використати колбеки або Promise‑chain, але це збільшує зв’язність.
8. Cheatsheet + follow-up
- Cheatsheet:
Subject.subscribe(o),unsubscribe(o),notify(data). - Interview Q: Як Observer допомагає розділити залежності? Чому важливо використовувати
Setдля списку observers?