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.

Связанные темы

Ресурсы