Observer Observable паттерн
Базовая модель публикации событий: Observable хранит коллекцию observer-ов, при вызове notify рассылает данные всем подписчикам. На функциях, на классах или на наследовании от EventEmitter.
Минимальная реализация на функциях
const observable = () => {
let observer = null;
setInterval(() => {
if (observer) observer(randomLetter);
}, 200);
return { subscribe: (fn) => { observer = fn; } };
};
const stream = observable;
stream.subscribe((char) => process.stdout.write(char));
Через функцию Subscribe (callback-контракт) мы можем подписаться. Это самая простая реализация на функциях.
Класс с одним подписчиком
class Observable {
subscribe(observer) {
this.observer = observer;
return this; // для chaining
}
notify(data) {
if (this.observer) this.observer(data);
}
}
Множество подписчиков
class Observable {
constructor { this.observers = ; }
subscribe(observer) { this.observers.push(observer); }
notify(data) {
for (const observer of this.observers) observer(data);
}
}
Могут быть более сложные реализации, которые имеют целую коллекцию функций — Set, Map, Array. Итерируемся и рассылаем всем.
Два контракта
| Сущность | Методы |
|---|---|
| Observable | subscribe, notify, complete |
| Observer | update |
Complete — это когда поток событий закончился.
Абстрактные методы через throw
class Observable {
complete { throw new Error('Not implemented'); }
}
В базовой реализации внутри просто бросается ошибка, чтобы их никто не создавал, не наследуя.
JS не имеет нативных abstract methods — имитируем через throw.
Конкретный поток через наследование
class CharStream extends Observable {
constructor {
super;
this.timer = setInterval(() => {
this.notify(randomLetter);
}, 200);
}
complete {
clearInterval(this.timer);
}
}
Мы не наследуем от таймера — таймер пишем в свойства. От Observable наследуем.
Композиция таймера + наследование Observable — пример "favor composition over inheritance" в гибридной форме.
Обратная ссылка observer ↔ observable
Observer, который приходит, в свойство Observable записывает ссылку на самого себя на this. Это необязательно — для обратной навигации.
Связь с EventEmitter
Node.js EventEmitter — то же самое с другими именами:
| Observable | EventEmitter |
|---|---|
| subscribe | on/addListener |
| notify | emit |
| complete | (нет, но removeListener/removeAllListeners) |
EventEmitter имеет ключ события — больше один stream по ключу. В Observable один stream на инстанс.
Связь с Rx
Observable из RxJS — это эта же модель + операторы (map/filter/etc) + lifecycle (cold/hot, completion). Минимальная Observable + операторы = Rx.
Связь с реактивностью
Observer — основа MVC, любых event-driven систем, реактивных UI, message bus. Когда речь о подписке на изменения — это observer.
Связанные темы
- RxJS и потоки событий
- Реактивное программирование основы
- Reactor pattern
- events -- EventEmitter
- Iterator Pattern
Ресурсы
- Лекция: _bFXuLcXoXg