# Observer паттерн в Javascript

poster
В этом видео мы с вами разберем такой паттерн, как Observer. Он достаточно сильно похож на Pub/Sub, но его идея немного в другом.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В этом видео мы с вами разберем такой паттерн, как Observer. Он достаточно сильно похож на Pub/Sub, но его идея немного в другом.

Каждый день, при разработке, у нас возникает проблема, когда у нас есть разные части приложения, которые должны реагировать на разные события. Например, пользователь ввел какой-то текст, и вам нужно изменить несколько компонентов. И, достаточно сложно, все это синхронизировать.

В этом нам может помочь Observer pattern. Он позволяет делать связи один ко многим между компонентами.

Мы хотим реализовать класс, у которого будут такие методы.

const observer = new EventObserver()

observer.subscribe(data => {
  console.log('subscribe was fired', data)
})

observer.broadcast({someData: 'hello'})

То есть мы создаем один обсервер и потом в нескольких местах подписываемся на события этого обсервера с помощью subscribe. Поэтому когда мы вызовем observer.broadcast, то это уведомит всех подписчиков.

Давайте попробуем реализовать этот паттерн самостоятельно.

class EventObserver {
  constructor () {
    this.observers = []
  }

  subscribe (fn) {
    this.observers.push(fn)
  }

  unsubscribe (fn) {
    this.observers = this.observers.filter(subscriber => subscriber !== fn)
  }

  broadcast (data) {
    this.observers.forEach(subscriber => subscriber(data))
  }
}

Мы создали класс, в котором мы храним массив подписчиков. Также мы описали методы subscribe, unsubscribe и broadcast.

Теперь давайте проверим, что этот код работает.

const observer = new EventObserver()

observer.subscribe(data => {
  console.log('subscribe for module 1 fired', data)
})

observer.subscribe(data => {
  console.log('subscribe for module 2 fired', data)
})

observer.broadcast({someData: 'hello'})

Если мы посмотрим в браузер, то мы видим два консоль лога с данными.

Теперь давайте попробуем на реальном примере. Мы хотим написать 2 компонента: в одном мы будем вводить данные, а другой будет выводить количество введенных слов.

Для начала давайте добавим 2 элемента в нашу разметку.

<textarea class='textField'></textarea>
<div>
  Words Count:
  <p class='countField'></p>
</div>

Теперь давайте создадим новый Observer и найдем наши 2 элемента.

const blogObserver = new EventObserver()

const textField = document.querySelector('.textField')
const countField = document.querySelector('.countField')

Теперь мы хотим подписаться на наш blogObserver.

blogObserver.subscribe(text => {
  console.log('broadcast catched')
})

И файрить broadcast, когда мы меняем текстовое поле

textField.addEventListener('keyup', () => {
  blogObserver.broadcast(textField.value)
})

Внутрь broadcast, в качестве данных, мы передали значение поля.

Если мы посмотрим в браузер, то при вводе данных у нас стреляет наш console.log. Это значит, что наш broadcast работает.

Теперь мы хотим из текста найти количество слов. Давайте напишем дополнительную функцию для этого.

const getWordsCount = text =>
  text ? text.trim().split(/\s+/).length : 0

И вызовем ее внутри subscribe.

blogObserver.subscribe(text => {
  countField.innerHTML = getWordsCount(text)
})

Если мы посмотрим в браузер, то все работает и количество слов меняется.

И, хоть это маленький пример, он отлично показывает как работает Observer паттерн.

  1. Сначала мы создаем новый экземпляр класс Observer. Например, для компонента поля.
  2. Дальше во всех других компонентах мы можем подписаться на broadcast этого обсервера.
  3. И теперь все наши компоненты синхронизированы и изменяются одновременно, при изменении данных.

Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Оксана Гаращенко
2 лет назад
1) unsubscribe никогда не сработает, потому что функции это по сути те же объекты, при сравнении 2 одинаковые функции дадут false, 2) если мы в 2 разных файлах вызовем new EventObserver - это будут 2 разных обсервера, а если я хочу использовать один и тот же обсервер в разных компонентах, например в одном компоненте я подписываюсь, а в другом отписываюсь?
Galeups
6 лет назад
Здравствуйте! Большое спасибо за видео. Объясниет пожалуйста: broadcast (data) { this.observers.forEach(subscriber => subscriber(data)) } Не совсем понял вот этот момент subscriber => subscriber(data) Ведь у нас у подписчиков нет метода subscriber(), как тогда он вызывается у каждого подписчика? Заранее спасибо!
monsterlessons
6 лет назад
Добрый день. У нас каждый подписчик сам по себе является функцией, которую мы и вызываем с нужными данными.
Stanislav V
7 лет назад
По-моему в примере использовано семантически неверное и вводящее в заблуждение название класса и объекта EventObserver и observer. Классический паттерн Observer опеределяет взаимоотношение между объектами, которыми являются Subject (то, что наблюдается) и собственно Observer (тот, кто наблюдает). За объектом Subject могут наблюдать множество Observers. В примере же объект который назван observer имеет методы подписки и отписки, и что-то там бродкастит, то есть на самом деле это Subject, observer должен только наблюдать. Слово Observer переводится как наблюдатель, он подписывается и наблюдает за чем-то, а не за ним наблюдают.
monsterlessons
7 лет назад
Да вы правы, надо было немного по другому написать пример. Спасибо за комментарий.
DIma Uchkin
6 лет назад
Вот тут можна найти классический Obeserver и отличия от Pub/Sub: https://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript
Алекс Бородулин
7 лет назад
Очень крутые уроки! Спасибо за труд) Этот канал вдохновил меня сделать блог с небольшими инструкциями по популярным библиотекам.
monsterlessons
7 лет назад
Спасибо. Рад, что вдохновил.
winlx
7 лет назад
У меня вопрос на который мне уже давно никто не может окончательно ответить. Вместо function declaration разработчики используют function expression при объявлении обычной функции, вот и тут в коде такое есть: const getWordsCount = text => text ? text.trim().split(/\s+/).length : 0 Много такого в исходниках redux, в туториалах по react так презентационные компоненты создают, да и вообще это довольно часто встречается. В чем "соль", может есть какие-то рекомендации?
monsterlessons
7 лет назад
Используйте то, что вам нравится, либо принято в команде, так как в javascript нет единого стиля. Я предпочитаю стрелочные функции, так как кода приходится писать меньше.
lisonok
7 лет назад
А подскажите еще какие то интересные примеры
monsterlessons
7 лет назад
Например у вас есть 3 диаграммы, подписанные но один и тот же источник данных. И когда он меняется, то вам нужно все эти диаграммы перерисовать.
lisonok
7 лет назад
А как это обычно реализовывается? Есть какая-та популярная библиотека с паттернами или под каждый проект отдельно пишутся?
monsterlessons
7 лет назад
Есть библиотеки, но не для всех паттернов и не в одной библиотеке. Например, обсервер или pub/sub часто встраиваются прямо в фреймворки.
lisonok
7 лет назад
А как применяются паттерны с реактом? В частности observer
monsterlessons
7 лет назад
Реакт - это только вью слой. Я не вижу кейза, когда обсервер было бы удобно с ним применять. Используйте готовые библиотеки при разработке. Например React + Redux дают вам готовую систему dispatch-subscribe. Лучше избегать написания велосипедов без необходимости.
Виталий Артюх
7 лет назад
Видео супер. Все доступно и понятно! Но почему нет ; в конце строки?)) как будто чего-то не хватает, так и хочется открыть исходник и проставить.
monsterlessons
7 лет назад
Потому что это лишний шум для чтения и в javascript существует всего 3 кейса, когда нужно ставить точку с запятой и вы обычно на них не попадете. Вот видео https://www.youtube.com/watch?v=gsfbh17Ax9I
Виталий Артюх
7 лет назад
можно не держать в памяти кейсы, и не думать, здесь нужно ставить, а здесь я не буду. Таким образом не ставить себя выше интерпретатора. Это больше похоже на вкусовщину, и дело привычки. есть еще такое видео: https://www.youtube.com/watch?v=5JEkiHHUOFs возможно старое и не касается ES6 но тем не мене, пока еще Babel доменирует перед браузерной поддержкой. В PHP отсутсвие semicolon это Parse error. JS слишком много прощает)) и еще. почему ссылки не ссылки?
monsterlessons
7 лет назад
Я и не держу их в памяти, так как на проектах никогда эти 3 кейса не пишутся. Вы говорите писать ; всегда, но я сомневаюсь, что вы пишите function a () { }; или class A { }; Поэтому так как интерпретатору без разницы, в каждой команде приняты свои правила.
Виталий Артюх
7 лет назад
в случае функции и класса скорее не пишу чем пишу, а вот конструкторе и блоке {} всегда пишу! это уже на подкорке отпечаталось, в конце инструкции ; так понятно, что закончилась одна инструкции и началась другая.
Sergey Illarionov
7 лет назад
Думаю, весьма полезно будет. Разница паттернов Pub-sub и Observer показана: - в картинке https://hsto.org/files/39b/7f9/806/39b7f98064b5458e9e2837cca15e3525.jpg - и в статье https://habrahabr.ru/post/270339/ Да, и спасибо за видео - замечательно!
monsterlessons
7 лет назад
Спасибо за картинки. Думаю, кому-то визуально будет проще представить как оно работает.