13. Observer
Design Patterns. Урок: Observer (Наблюдатель)
Заголовок раздела «Design Patterns. Урок: Observer (Наблюдатель)»Observer — поведенческий паттерн, создающий механизм подписки, который позволяет одним объектам следить за событиями в других.
Это основа событийного программирования. DOM events, RxJS, Redux, Node.js EventEmitter — всё это Observer в разных формах.
Принцип работы
Заголовок раздела «Принцип работы»Есть Subject (источник событий) и Observers (подписчики). Subject уведомляет всех подписчиков при изменениях.
// Интерфейсыinterface Observer<T> { update(data: T): void;}
interface Subject<T> { subscribe(observer: Observer<T>): void; unsubscribe(observer: Observer<T>): void; notify(data: T): void;}
// Базовая реализация Subjectclass EventEmitter<T> implements Subject<T> { private observers: Set<Observer<T>> = new Set();
subscribe(observer: Observer<T>): void { this.observers.add(observer); }
unsubscribe(observer: Observer<T>): void { this.observers.delete(observer); }
notify(data: T): void { this.observers.forEach(observer => observer.update(data)); }}
// Пример: корзина покупокinterface CartEvent { type: 'item_added' | 'item_removed' | 'cart_cleared'; cartId: string; item?: CartItem;}
class ShoppingCart extends EventEmitter<CartEvent> { private items: CartItem[] = [];
constructor(private readonly cartId: string) { super(); }
addItem(item: CartItem): void { this.items.push(item); this.notify({ type: 'item_added', cartId: this.cartId, item }); }
removeItem(itemId: string): void { const item = this.items.find(i => i.id === itemId); this.items = this.items.filter(i => i.id !== itemId); this.notify({ type: 'item_removed', cartId: this.cartId, item }); }
clear(): void { this.items = []; this.notify({ type: 'cart_cleared', cartId: this.cartId }); }}
// Наблюдатели реагируют на событияclass AnalyticsTracker implements Observer<CartEvent> { update(event: CartEvent): void { if (event.type === 'item_added') { analytics.track('product_added_to_cart', { product_id: event.item?.id, cart_id: event.cartId, }); } }}
class StockChecker implements Observer<CartEvent> { update(event: CartEvent): void { if (event.type === 'item_added' && event.item) { inventoryService.reserveItem(event.item.id); } if (event.type === 'item_removed' && event.item) { inventoryService.releaseItem(event.item.id); } }}
class CartPersister implements Observer<CartEvent> { update(event: CartEvent): void { cartRepository.saveEvent(event); }}
// Подключаем наблюдателейconst cart = new ShoppingCart('cart-123');cart.subscribe(new AnalyticsTracker());cart.subscribe(new StockChecker());cart.subscribe(new CartPersister());
cart.addItem({ id: 'prod-1', name: 'Laptop', price: 999 });// Все три наблюдателя получат уведомлениеФункциональный Observer: Event Emitter
Заголовок раздела «Функциональный Observer: Event Emitter»// Типизированный Event Emittertype EventMap = Record<string, any>;type EventKey<T extends EventMap> = string & keyof T;type EventCallback<T extends EventMap, K extends EventKey<T>> = (payload: T[K]) => void;
class TypedEventEmitter<T extends EventMap> { private handlers: Partial<{ [K in EventKey<T>]: Set<EventCallback<T, K>> }> = {};
on<K extends EventKey<T>>(event: K, handler: EventCallback<T, K>): () => void { if (!this.handlers[event]) { this.handlers[event] = new Set(); } (this.handlers[event] as Set<EventCallback<T, K>>).add(handler);
// Возвращаем функцию отписки return () => this.off(event, handler); }
off<K extends EventKey<T>>(event: K, handler: EventCallback<T, K>): void { this.handlers[event]?.delete(handler as any); }
emit<K extends EventKey<T>>(event: K, payload: T[K]): void { this.handlers[event]?.forEach(handler => handler(payload)); }}
// Использование с типамиinterface AppEvents { 'user:login': { userId: string; timestamp: Date }; 'user:logout': { userId: string }; 'order:created': { orderId: string; total: number };}
const eventBus = new TypedEventEmitter<AppEvents>();
// TypeScript подскажет типы!const unsubscribe = eventBus.on('user:login', ({ userId, timestamp }) => { console.log(`User ${userId} logged in at ${timestamp}`);});
eventBus.emit('user:login', { userId: 'u-123', timestamp: new Date() });
// Отпискаunsubscribe();Observer в React (useState, useContext)
Заголовок раздела «Observer в React (useState, useContext)»React — это Observer паттерн. Компоненты «наблюдают» за состоянием:
// Кастомный Observable Storeclass Store<T> { private state: T; private listeners = new Set<(state: T) => void>();
constructor(initialState: T) { this.state = initialState; }
getState(): T { return this.state; }
setState(updater: (prev: T) => T): void { this.state = updater(this.state); this.listeners.forEach(listener => listener(this.state)); }
subscribe(listener: (state: T) => void): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); }}
// React hook для подписки на Storefunction useStore<T>(store: Store<T>): T { const [state, setState] = useState(store.getState());
useEffect(() => { const unsubscribe = store.subscribe(setState); return unsubscribe; // Автоматическая отписка при unmount }, [store]);
return state;}Практические задания
Заголовок раздела «Практические задания»-
Реализуй
FormObserver— наблюдатель за изменениями полей формы, который обновляет счётчик заполненных полей. -
Создай
NetworkStatusObserver— следит за состоянием сети (online/offline) и уведомляет подписчиков. -
Напиши Observer для real-time валидации формы — каждое поле подписано на события изменений других полей.
-
В чём разница между Observer и EventEmitter на практике?
-
Изучи как RxJS расширяет концепцию Observer: что такое
Observable,Subject,BehaviorSubject?