Перейти к содержимому

89. Observer (Наблюдатель)

Observer — поведенческий паттерн, в котором объект (Subject) ведёт список наблюдателей (Observers) и уведомляет их об изменениях. Это основа для систем событий, реактивного программирования и EventEmitter.

class EventEmitter {
#events = new Map()
on(event, listener) {
if (!this.#events.has(event)) {
this.#events.set(event, new Set())
}
this.#events.get(event).add(listener)
return () => this.off(event, listener) // возвращаем функцию отписки
}
off(event, listener) {
this.#events.get(event)?.delete(listener)
return this
}
emit(event, ...args) {
this.#events.get(event)?.forEach(listener => listener(...args))
return this
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args)
this.off(event, wrapper)
}
return this.on(event, wrapper)
}
}
// Использование
const emitter = new EventEmitter()
const unsubscribe = emitter.on('data', (value) => {
console.log('Получено:', value)
})
emitter.emit('data', 42) // 'Получено: 42'
emitter.emit('data', 'hello') // 'Получено: hello'
unsubscribe() // отписываемся
emitter.emit('data', 'ещё') // ничего — подписка снята
class Store extends EventEmitter {
#state
constructor(initialState) {
super()
this.#state = initialState
}
get state() { return { ...this.#state } }
setState(updates) {
const prevState = this.#state
this.#state = { ...this.#state, ...updates }
// Уведомляем о каждом изменённом поле
for (const key in updates) {
if (prevState[key] !== this.#state[key]) {
this.emit(`change:${key}`, this.#state[key], prevState[key])
}
}
this.emit('change', this.#state, prevState)
}
}
const store = new Store({ count: 0, user: null, theme: 'dark' })
// Подписка на конкретное поле
store.on('change:count', (newVal, oldVal) => {
console.log(`count: ${oldVal} → ${newVal}`)
})
// Подписка на любое изменение
store.on('change', (state) => {
document.title = `Счёт: ${state.count}` // обновляем заголовок
})
store.setState({ count: 1 }) // count: 0 → 1
store.setState({ count: 5 }) // count: 1 → 5
store.setState({ user: { name: 'Яша' } }) // только 'change' событие
// Интерфейс наблюдателя
class Observer {
update(data) { throw new Error('Не реализовано') }
}
// Тема (Subject)
class WeatherStation {
#observers = new Set()
#temperature = 0
#humidity = 0
subscribe(observer) { this.#observers.add(observer); return this }
unsubscribe(observer) { this.#observers.delete(observer); return this }
#notify() {
const data = { temperature: this.#temperature, humidity: this.#humidity }
this.#observers.forEach(obs => obs.update(data))
}
setWeather(temp, humidity) {
this.#temperature = temp
this.#humidity = humidity
this.#notify()
}
}
// Конкретные наблюдатели
class TemperatureDisplay extends Observer {
update({ temperature }) {
console.log(`🌡️ Температура: ${temperature}°C`)
}
}
class HumidityDisplay extends Observer {
update({ humidity }) {
console.log(`💧 Влажность: ${humidity}%`)
}
}
class AlertSystem extends Observer {
update({ temperature, humidity }) {
if (temperature > 35) console.log('🔥 ALERT: Высокая температура!')
if (humidity > 80) console.log('💦 ALERT: Высокая влажность!')
}
}
const station = new WeatherStation()
const tempDisplay = new TemperatureDisplay()
const humidDisplay = new HumidityDisplay()
const alerts = new AlertSystem()
station
.subscribe(tempDisplay)
.subscribe(humidDisplay)
.subscribe(alerts)
station.setWeather(25, 60) // Обычная погода
station.setWeather(37, 85) // ALERT!
station.unsubscribe(humidDisplay)
station.setWeather(20, 50) // humidDisplay больше не получает обновления
function createReactive(target, onChange) {
return new Proxy(target, {
set(obj, key, value) {
const old = obj[key]
obj[key] = value
if (old !== value) onChange(key, value, old)
return true
}
})
}
const state = createReactive({ name: 'Яша', age: 1 }, (key, val, old) => {
console.log(`${key}: ${old} → ${val}`)
// обновляем DOM или что-то ещё
})
state.name = 'Алексей' // name: Яша → Алексей
state.age = 2 // age: 1 → 2

✅ Observer подходит для:

  • Системы событий / EventBus
  • Реактивные UI фреймворки (React state, Vue reactive)
  • WebSocket уведомления
  • Живые дашборды и real-time данные
  • MVC: Model уведомляет View