# Publish/subscribe в Javascript

poster
В этом видео мы разберем такой популярный и часто используемый паттерн, как pubSub или publish/subscribe.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В этом видео мы разберем такой популярный и часто используемый паттерн, как pubSub или publish/subscribe.

Какую проблему решает pubsub?

Представим, что у нас есть онлайн магазин, который при новом заказе посылает емейл юзеру. Давайте напишем простую реализацию.

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

const order = new Order({userEmail: 'john@gmail.com'})
order.save()

Давайте добавим класс Order

class Order {
  constructor (params) {
    this.params = params
  }

  save () {
    console.log('Order saved')
    this.sendEmail(this.params)
  }

  sendEmail () {
    const mailer = new Mailer()
    mailer.sendPurchaseEmail(this.params)
  }
}

Мы сохраняем параметры в this и при вызове save отправляем емейл. Емейл мы отправляем с помощью другого класса Mailer.

Давайте добавим его сейчас.

class Mailer {
  sendPurchaseEmail (params) {
    console.log(`Email send to ${params.userEmail}`)
  }
}

Если мы посмотрим в браузер, то мы получаем сообщение Order saved и Email send.

Какая же у нас есть здесь проблема? Классы Order и Mailer очень тесно связаны. Это обычно значит, что когда мы заходим обновить один класс, то нам прийдется обновлять и другой. Например, если мы захотим поменять имя sendPurchaseEmail, или его параметры, то нам прийдется менять класс Order тоже.

Поэтому всегда нужно стараться, чтобы компоненты не были тесно связаны.

Вторая проблема заключается в том, что если мы будем покрывать класс Order тестами, нам прийдется много чего мокать.

Такие проблемы решаются с помощью паттерна publish/subscribe. То есть, в приложении мы паблишим события без привязки к какому-то конкретному классу. И мы можем создавать подписчиков (subscribers), которые будут слушать события, которые им интересны.

Это позволяет не делать зависимости между компонентами системы.

Давайте сейчас создадим нашу pubsub систему и улучшим наши классы Order и Mailer.

Мы хотим реализовать вот такое использование нашего PubSub.

EventBus.subscribe('foo', () => console.log('foo fired'))
EventBus.publish('foo', 'Hello world')

То есть, сначала, с помощью subscribe мы подписались на ивент foo, и когда он выстрелит, то наш коллбек, который мы передали вторым параметром, выполнится. Потом мы выстрелили publish с ивентом foo и передали в параметрах текст Hello world.

Давайте теперь опишем EventBus.

const EventBus = {
  channels: {},
  subscribe (channelName, listener) {
    if (!this.channels[channelName]) {
      this.channels[channelName] = []
    }
    this.channels[channelName].push(listener)
  },

  publish (channelName, data) {
    const channel = this.channels[channelName]
    if (!channel || !channel.length) {
      return
    }

    channel.forEach(listener => listener(data))
  }
}
  1. Мы создали объект channels, в котором мы будем хранить наши каналы. Например, foo у нас будет каналом.
  2. В методе subscribe мы проверяем - есть ли канал и если нет - создаем. Потом, пушим в созданный канал новый listener.
  3. В publish мы проходимся по слушателям канала и вызываем их. Если канала или слушателей нет, то ничего не делаем.

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

Это самый простой вариант реализации pubsub. Если вы хотите посмотреть на готовые и более сложные реализации pubsub, то советую библиотеку PubSubJS.

Теперь давайте обновим наш пример с Order и Mailer.

class Order {
  constructor (params) {
    this.params = params
  }

  save () {
    console.log('Order saved')
    EventBus.publish('order/new', {
      userEmail: this.params.userEmail
    })
  }
}

class Mailer {
  constructor () {
    EventBus.subscribe('order/new', this.sendPurchaseEmail)
  }

  sendPurchaseEmail (params) {
    console.log(`Email send to ${params.userEmail}`)
  }
}

const mailer = new Mailer()
const order = new Order({userEmail: 'john@gmail.com'})
order.save()

Теперь мы создаем Mailer отдельно. При инициализации он подписывается на канал order/new и когда кто-то запаблишит событие, вызовет sendPurchaseEmail.

В Order мы полностью убрали зависимость от Mailer и просто паблишим событие order/new, передавая в него нужные данные.

Если мы посмотрим в браузер, то все работает, как и раньше, но теперь Mailer и Order никак не связаны, мы можем менять любой функционал внутри и ничего не поломать в другом классе.

Какие же плюсы у этого паттерна?

  1. Мы можем легко разбивать приложение на независимые части
  2. Мы можем легко реализовывать слабую связность

Какие же есть минусы у данного паттерна?

  1. Мы никогда не знаем, а подписан ли на нас кто-то. Поэтому, если в subscribe что-то отломалось, то паблиш никогда этого не узнает.
  2. Также, достаточно часто, в больших проектах количество паблишей и сабскрайбов разрастается и они становятся крайне запутанными. Начиная от того, что вам нужно пройти по цепочке из нескольких паблишей, чтобы добраться до реальной функции, которая что-то делает, и заканчивая циклической зависимостью, когда компоненты подписаны друг на друга и вы, проходя по ивентам, возвращаетесь в начальный класс.

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

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Sergey Illarionov
4 месяцев назад
Почему EventBus реализован объектом, а не классом, скажем?
monsterlessons
4 месяцев назад
Потому что из него не нужно создавать экземпляры класса. У нас всегда только один обьект с которым мы работает. Но вы, конечно, могли бы сделать его классом и просто написать eventBus = new EventBus() но он у вас все равно один на все приложение. Как вариант можно было сделать его синглтоном.
Sergey Illarionov
4 месяцев назад
Про синглтон отдельно ждем видео.
monsterlessons
4 месяцев назад
Есть же уже https://monsterlessons.com/project/lessons/singleton-pattiern-v-javascript
Sergey Illarionov
4 месяцев назад
Идеально, спасибо.
Galeups
4 месяцев назад
Спасибо большое. Как раз начал интересоваться паттернами JS.
monsterlessons
4 месяцев назад
На здоровье
Sergey Illarionov
4 месяцев назад
Спасибо за видео. И очень надеюсь, что предполагается серия видео о о иных дизайн-паттернах в JS, таких как: Decorator, Factory и т.д.
monsterlessons
4 месяцев назад
На здоровье. Да, будет снята вся серия.
Sergey Illarionov
4 месяцев назад
Заранее ликую и жду выпусков!
winlx
4 месяцев назад
Серия по паттернам, как раз то что искал.
monsterlessons
4 месяцев назад
Все будет по обычному графику вт и суббота, за исключением отпуска.