# 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. И теперь все наши компоненты синхронизированы и изменяются одновременно, при изменении данных.

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

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