Observer и Pub-sub, наверное самые известные паттерны взаимодействия в мире разработки интерфейсов и JavaScript. Но несмотря на свою известность, некоторые разработчики считают эти паттерны одинаковыми, что и послужило подспорьем написать данную статью.





Существует довольно много шаблонов проектирования, например, фасад, фабрика, адаптер, мост и др. Считается, что каждый разработчик применяет паттерны проектирования, просто решая повседневные задачи, при этом возможно он даже и не знает об их существовании. Например, такие паттерны, как фасад, фабрика или адаптер, вполне можно применять даже не изучая их, т.к. они довольно элементарные и являются логическим заключением при решении каких-либо задач. На мой взгляд, лучший паттерн — это наличие мозга в голове и желания сделать качественный продукт, ведь паттерны сами по себе не являются конечным решением, а дают всего лишь общее решение для типичных задач.

Но в мире интерфейсов и асинхронного JavaScript не возможно прожить без знания паттернов observer и pub-sub, так как знание и понимание этих шаблонов, значительно облегчает жизнь разработчику.

Observer и Pub-sub относятся к поведенческим паттернам (паттернам взаимодействия), т.е. применяются, когда требуется организовать взаимодействие между различными объектами системы.

Observer


Observer представляет собой не что иное, как связь один-ко-многим. В упрощенном виде этот паттерн состоит из объекта наблюдения (subject) и наблюдателей (observers).

Принципиальная схема взаимодействия выглядит так:

Subject — реализует методы: observe, detach, notify, get, set.

Observer — реализует метод update.

Также subject содержит ссылки на всех observers, которые его слушают, а observer, в свою очередь содержит ссылку, на subject, на который он подписан.

Таким образом в этом паттерне наблюдается прямая связь между объектами, т.е. subject знает о всех своих observers и вручную оповещает их о происходящих в себе изменениях, вызывая метод update у каждого observer. Связь устанавливается методом observe, разрывается методом detach.

Subject хранит внутри себя свое состояние и все действия с его состоянием необходимо совершать используя get/set методы, чтобы при изменениях состояния вызывать метод notify. Подобная схема реализована в EmberJs.

Observer можно представить довольно неплохой картинкой (заимствовано тут):

Примерную реализацию можно найти на сайте Addy Osmani.

Pub-sub



Pub-sub паттерн является одной из вариаций паттерна Observer. Исходя из названия в паттерне выделяют два компонента Publisher (издатель) и Subscriber (подписчик). В отличие от Observer, связь между объектами осуществляется посредством канала связи Event Channel (шины событий).

Publisher кидает свои события в Event Channel, а Subscriber подписывается на нужное событие и слушает его на шине, что обеспечивает отсутствие прямой связи между подписчиком и издателем.

Схематично Pub-sub и отличие от Observer, можно представить так:


Таким образом можно выделить основные отличительные особенности между Pub-sub и Observer:
  1. отсутствие прямой связи между объектами
  2. объекты сигнализируют друг другу событиями, а не состояниями объекта
  3. возможность подписываться на различные события на одном объекте с различными обработчиками


Одной из наиболее известных реализаций паттерна pub-sub является Backbone, AmplifyJs и др. DOM, в некоторой степени тоже реализует модель pub-sub.

Mediator



На основе pub-sub строится работа паттерна Mediator, который позволяет наладить коммуникацию между различными компонентами системы. Mediator представляет собой глобальный объект в системе, о котором знают все компоненты системы, при этом компонент может выступать как слушателем события, так и издателем другого события, таким образом налаживая коммуникацию между объектами системы.

Если провести аналогию, то Mediator это городская АТС, в которую приходят входящие и исходящие вызовы от абонентов, а доходят они строго до нужного абонента. Но как мы знаем у телефонной сети есть недостаток — на новый год она может оказаться перегруженной огромным количеством звонков и перестать доставлять вызова абонентам. Тоже самое может произойти и с Mediator, когда он не справится с потоком событий.

Mediator особенно полезен в тех случаях, когда наблюдаются множественные однонаправленные или двунаправленные связи между различными компонентами системы. Особенно паттерн полезен, когда в приложении имеются вложенные друг в друга компоненты системы (например, дочерние композиционные элементы), чтобы не было необходимости пробрасывать callbacks используя модель всплытия события изнутри наружу. Достаточно предоставить Mediator внутреннему компоненту, который опубликует свое событие, а другие компоненты узнают об этом событии.

Mediator паттерн довольно успешно реализован в Backbone — сам глобальный объект Backbone можно использовать в качестве Mediator, либо унаследоваться от Backbone.Events.

Что? Где? Когда?



Когда и где следует применять каждый паттерн — дело каждого, но прежде чем применять, следует понять их отличия и особенности. Например, Observer паттерн представляет собой прямую связь между объектами и сигнализирует наблюдателю об изменении своего состояния. На мой взгляд, данный паттерн очень хорошо подходит при разработке различных форм с множеством полей ввода, когда необходимо реагировать на изменения значений полей формы (binding).

Паттерн Mediator, как уже было отмечено, особенно полезен, когда необходимо наладить однонаправленную или двунаправленную связь между разрозненными компонентами системы, например, в сложных интерфейсах с множеством композиционных элементов, когда необходимо пробросить событие из вложенного view наверх, чтобы в другом view отобразить какую-либо информацию или выполнить действие, зависящее от этого события.

Почитать


  • Learning Javascript Design Patters, Addy Osmani
  • Programming in the large with Design Patterns, Eddie Burris
  • Примеры кода
Observer отличается от Pub-sub?

Проголосовало 246 человек. Воздержалось 59 человек.

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

Комментарии (43)


  1. PerlPower
    12.11.2015 01:38
    +15

    Когда я первый раз прочитал книжку про паттерны, то не понял зачем это все надо. Когда годы спустя я решил еще раз въехать в паттерны, то понял что мне это уже не надо. И в этом проблема всей литературы по паттернам — либо ты их не понимаешь, либо доходишь до них сам. А упражнения в словесности на тему, в чем разница между Provider и Repository, Observer и Pub-sub, Dependecy Injection и Service Locator, они до боли напоминают богословские дискуссии. Много воды, мало сути, и никакого практического результата.


    1. musuk
      12.11.2015 06:33
      +21

      Почему же? Паттерны ведь нужны, чтобы на собеседовании про них говорить.


      1. ekubyshin
        12.11.2015 10:44
        -3

        да, практика показывает, что о паттернах знают довольно толковые разработчики с хорошим опытом. На собеседованиях, кто не смог ответить про паттерны или вообще впервые слышал о них, тот как правило мало опыта имел в разработке, т.е. корреляция есть.


        1. PerlPower
          12.11.2015 13:10
          +5

          А моя практика показывает, что и вчерашний студент, у которого недавно был курс объектного проектирования, помнит названия шаблонов и их определения, не понимая, что за ними стоит.


          1. ekubyshin
            12.11.2015 13:15
            +1

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


            1. PerlPower
              12.11.2015 13:45
              +3

              Видимо с высоты вашего опыта и видна. Я например не могу определить кто передо мной — человек с хорошей памятью или профессиональный разработчик. Можно конечно спрашивать не про паттерны, но давать словесно описание какого-то компонента, а затем просить собеседуемого идентифицировать что это за паттерн. Но тогда могут начаться «ложные срабатывания» на студентов — вы только что сами посвятили статью вопросу, что шаблоны путают между собой.


              1. lair
                13.11.2015 15:56

                Мы делаем ровно наоборот: если человек написал в резюме, что он знает и применяет паттерны, то на собеседовании его просят описать один-два известных ему паттерна, включая формальное определение, достоинства, недостатки, и, самое главное, где конкретно в его опыте этот паттерн применялся, и чем это применение было обосновано.


            1. Atos
              12.11.2015 19:13

              Да, это разные вещи. Я, пока читал GoF, всё или почти всё понимал по сути, а вот потом из памяти выудить названия и связь с идеями реализации… с зазубриванием определений всегда плохо было. Впрочем, знания и освежить можно легко, имея книжку под рукой.


        1. Gorthauer87
          13.11.2015 15:37
          +1

          Если спрашивать по названиям паттернов, то разумеется, их я тоже не помню на память. Но если попросить что-то сделать, то я покажу умение владеть ими, даже если запамятовал названия.


    1. ekubyshin
      12.11.2015 10:46

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


      1. PerlPower
        12.11.2015 13:39
        +1

        А что если дело в том, что вы именно воспитываете программистов, делая так, чтобы они понимали вещи так же как и вы, а дело вовсе не в паттернах? Они смотрят на код и запоминают, что вот этот набор классов тимлид решил называть Observer, и т.д. Т.е. вы просто в процессе воспитания вырабатываете общую терминологию. А например если перенести их или тимлида в другую команду, то понимания такого уже не будет.


    1. i360u
      12.11.2015 13:01
      +2

      Это все бывает полезно, когда нужно быстро объяснить как приложение спроектировано, при условии, что собеседник о этих паттернах имеет какое-то представление. Каждый раз рисовать схему и рассказывать почему именно так — бывает утомительно. А в целом — конечно да: паттерны становятся нужны только в тот момент, когда ты сам до них дошел и потом просто узнал как это называется.


      1. PerlPower
        12.11.2015 13:32
        +5

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


        И при условии, что его представление о том, что из себя представляет паттерн, не отличается от вашего. Начните набирать в гугле "<имя паттерна> vs" и посмотрите какая там путаница.

        Каждый раз рисовать схему и рассказывать почему именно так — бывает утомительно.


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


        1. i360u
          12.11.2015 13:38

          Мы о разном: я — о коммуникациях, вы — о непосредственной реализации. В части реализации — я полностью согласен, но базовые представления о паттернах просто полезны в общении. Если углубляться — да, начинается путанница и все эти религиозные споры становятся пустой тратой времени.


        1. ekubyshin
          12.11.2015 13:40

          и ты только что пересказал мой второй абзац в тексте, только другими словами. Паттерны дают лишь общее понятие для типовых решений, никто не утверждает, что паттерн является конечным решением и надо организовывать код именно так. В любом учебнике по паттернам это и говорится сразу же.


        1. funca
          14.11.2015 10:55
          -1

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

          Условно, в мире вещей есть такой конструкторский прием — контрагайка, который при вибрациях повышает надежность, не давая соединению развинчиваться. Технически, это когда на один болт накручивается вместо одной гайки — две. Причем, это могут быть абсолютно одинаковые гайки. Т.е. с точки зрения слесаря, задача которого изготовить гайки, между гайками нет ни какой разницы. Разница заметна только на уровне инженера. Слесари могут искренне «не понимать», зачем нужны все эти конструкторские приемы, когда в результате от них требуется одно и то же.

          Так вот, отношение к разработчика паттернам на собеседовании показывает в какой роли он себя представляет «слесаря-кодера» или «инженера-архитектора», и соответственно определяет типы задач, решение которых ему можно будет доверить.


          1. PerlPower
            14.11.2015 13:05
            +2

            Условно, в мире вещей есть такой конструкторский прием — контрагайка, который при вибрациях повышает надежность, не давая соединению развинчиваться. Технически, это когда на один болт накручивается вместо одной гайки — две. Причем, это могут быть абсолютно одинаковые гайки. Т.е. с точки зрения слесаря, задача которого изготовить гайки, между гайками нет ни какой разницы. Разница заметна только на уровне инженера. Слесари могут искренне «не понимать», зачем нужны все эти конструкторские приемы, когда в результате от них требуется одно и то же.

            Так вот, отношение к разработчика паттернам на собеседовании показывает в какой роли он себя представляет «слесаря-кодера» или «инженера-архитектора», и соответственно определяет типы задач, решение которых ему можно будет доверить.


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

            Кстати вчерашний студент, который бьет себя в грудь и кричит что он любит паттерны, правильные архитектуры, и вообще он за все хорошее и против всего плохого, вот он имеет правильное отношение для «инженера-архитектора»?


            1. funca
              15.11.2015 21:18

              Читайте внимательнее, и не только свои комментарии.

              Кстати вчерашний студент, который бьет себя в грудь и кричит что он любит паттерны, правильные архитектуры, и вообще он за все хорошее и против всего плохого, вот он имеет правильное отношение для «инженера-архитектора»?

              Думаю, что все зависит от уровня владения предметом и навыков — результат определяется как уровнем теоретической подготовки, так и практическим опытом. Мне сложно судить про абстрактных студентов, у нас работает несколько старшекурсников, отличные ребята, у которых за плечами уже с десяток проектов.

              Расскажите, приходится-ли вам на работе сталкиваться с однотипными проблемами и используете-ли вы в таких случаях какие-то типовые решения? Как вы делитесь ими с коллегами?


              1. PerlPower
                16.11.2015 03:45

                Думаю, что все зависит от уровня владения предметом и навыков — результат определяется как уровнем теоретической подготовки, так и практическим опытом. Мне сложно судить про абстрактных студентов, у нас работает несколько старшекурсников, отличные ребята, у которых за плечами уже с десяток проектов.


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

                Расскажите, приходится-ли вам на работе сталкиваться с однотипными проблемами и используете-ли вы в таких случаях какие-то типовые решения? Как вы делитесь ими с коллегами?


                Решения однотипных проблем идут в вики проекта, если это что-то общее. Или в код библиотеки утилит, модуля, сниппета и т.п. если это можно решить кодом.


    1. yul
      13.11.2015 09:13
      -2

      Есть люди, которые умудряются читать нужные книжки вовремя ;)


    1. VolCh
      13.11.2015 13:40

      Назначение паттернов по сути одно — дать единое название типичному способу решения типичной задачи. Описание паттерна, это не императив «это задачу нужно решать так», это декларация «это решение этой задачи называется <pattern_name>». Всё. Фраза «что мне это уже не надо» говорит о том, что вам уже не надо кратко объяснять кому-то (в том числе путем именования артефактов кода) как работает ваш код. Или читай мануал юзера, или читай код досконально.


  1. vintage
    12.11.2015 09:05
    -5

    Также стоит почитать эту статью, где рассказывается почему event-based programming — это тупиковый путь развития :-) http://habrahabr.ru/post/235121/


    1. lair
      12.11.2015 12:13
      +2

      Угу, вы считаете, что Rx — тупиковый путь развития?


      1. vintage
        12.11.2015 20:55
        -3

        Именно так.


        1. lair
          12.11.2015 20:57

          Ууу… пожалуй нет, спасибо.


          1. vintage
            12.11.2015 21:21
            +1

            Боюсь мне нечего ответить на столь убедительный аргумент с вашей стороны :-)


            1. lair
              12.11.2015 21:58
              +2

              Да я, собственно, и имею в виду, что обсуждать тут нечего.


              1. vintage
                13.11.2015 02:34
                +2

                И правда, ведь все знают, что Rx — это манна небесная и нечего тут еретику что-то объяснять :-)


                1. lair
                  13.11.2015 11:35

                  Да нет, просто обсуждение Rx в этой статье достаточно бессмысленно. Кому надо — и так прочитает.


                  1. vintage
                    14.11.2015 00:58

                    Ну, я вот почитал, и как-то не уверовал, что императивная реактивность спасёт мир.

                    Как-то не вижу я преимуществ у такого кода:

                    var message = config.flatMapLatest( function( config ) {
                    if( config ) {
                    return mouseCoords.map( function( coords ) {
                    return 'Mouse coords is ' + coords
                    }
                    } else {
                    return mouseTarget.map( function( target ) {
                    return 'Mouse target is ' + target
                    }
                    }
                    } )

                    Перед таким:

                    var message = $jin.atom.prop( {
                    pull: function( ) {
                    if( config.get() ) {
                    return 'Mouse coords is ' + coords.get()
                    } else {
                    return 'Mouse target is ' + target.get()
                    }
                    }
                    } )


                    1. lair
                      14.11.2015 01:17
                      +1

                      Ну не видите — и ладно, значит, вам Rx не поможет.

                      (хотя, конечно, приведенный вами код к Rx отношение имеет весьма условное)


                      1. vintage
                        14.11.2015 10:38
                        +2

                        А может всё-таки откроете мне глаза, какую такую великую идею я тут не замечаю? :-)

                        Первый код на RxJS.


                        1. lair
                          14.11.2015 17:17

                          А может всё-таки откроете мне глаза, какую такую великую идею я тут не замечаю?

                          А смысл? Я не знаю, ваших задач, а применимость сильно зависит от задачи.

                          Первый код на RxJS.

                          То, что код на чем-то написан, еще не означает, что это идиоматичный код.


                          1. vintage
                            14.11.2015 17:23
                            +2

                            Опишите задачу, где Rx покажет себя лучше :-)

                            Вам же не сложно будет переписать этот код в более идиоматичном виде?


                            1. lair
                              14.11.2015 17:27

                              Опишите задачу, где Rx покажет себя лучше

                              Лучше, чем что? Вот вам задача, где Rx показывает себя вполне неплохо.

                              Вам же не сложно будет переписать этот код в более идиоматичном виде?

                              А какую задачу он решает?


                              1. vintage
                                14.11.2015 19:32
                                -1

                                Лучше, чем атомы. Замечательно, как временно поставить распознавание морзе на паузу так чтобы стримы не работали вхолостую на каждое нажатие клавиши?

                                Переключает источники данных в зависимости от настроек.


                                1. lair
                                  14.11.2015 19:46

                                  Лучше, чем атомы.

                                  Я не знаю, как работают атомы, поэтому не могу сравнивать.

                                  Замечательно, как временно поставить распознавание морзе на паузу так чтобы стримы не работали вхолостую на каждое нажатие клавиши?

                                  Вставить IConnectableObservable (не знаю эквивалент для JS), подписки диспозить. Но сам не проверял, честно.

                                  Переключает источники данных в зависимости от настроек.

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

                                  Будем честными, основной ужас вашего кода (обоих из них) вызван тем, что в JS кривые анонимные функции, а не фреймворком.

                                  var message = config
                                    .SelectMany(config => config
                                      ? mouseCoords.Select(coords => "Mouse coords is " + coords)
                                      : mouseTarget.Select(target => "Mouse target is " + target)
                                    .Switch();
                                  


                                  1. vintage
                                    14.11.2015 20:33

                                    Значит у вас есть уникальная возможность узнать что-то новое ;-)

                                    Диспозить конечно же ручками, да? :-) Там в статье про это всё есть и в продолжении конкретно про Rx — http://habrahabr.ru/post/240773/

                                    Ну да, вставить нереактивный костыль проще, чем делать идеоматично на Rx, только потом от таких костылей огребаешь потерей консистентности.

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


                                    1. lair
                                      14.11.2015 20:52

                                      Значит у вас есть уникальная возможность узнать что-то новое

                                      Спасибо, не вижу ничего уникального в этой возможности.

                                      Диспозить конечно же ручками, да?

                                      Как паузить, так и диспозить.

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

                                      Не те же. Покажите мне «оператор», эквивалентный flatMap.

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

                                      У меня создается ощущение, что вы просто пытаетесь применить Rx для задач, где ему плохо.


                                      1. vintage
                                        14.11.2015 21:11
                                        -1

                                        Попробуйте снять очки суперзвезды :-)

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

                                        Ну да, и добавляет свои «операторы», которые без этой библиотеки и не нужны бы были :-)

                                        То есть вы подтверждаете, что Rx не подходит для разработки больших и динамичных веб приложений? :-)


                                        1. lair
                                          14.11.2015 21:22

                                          В общем случае, без дополнительных костылей, вы не знаете можно ли диспозить стрим, так как от него кто-то может зависеть.

                                          Так не надо диспозить стрим, надо диспозить свою подписку (от которой точно никто не зависит).

                                          Ну да, и добавляет свои «операторы», которые без этой библиотеки и не нужны бы были

                                          FlatMap — это всего лишь слияние двух последовательностей (если мы говорим об Enumerable или Observable).

                                          То есть вы подтверждаете, что Rx не подходит для разработки больших и динамичных веб приложений?

                                          Нет, не подтверждаю.


  1. Zhendalf
    12.11.2015 21:34

    Замечательно написанная статья. Спасибо. Полезно иметь под рукой названия постоянно используемых паттернов.


  1. Serg046
    16.11.2015 19:34

    На основе pub-sub строится работа паттерна Mediator

    Не согласен. Mediator может быть реализован как угодно.

    Более того, скорее pub-sub — это реализация mediator, но не наоборот.
    Более того, скорее это даже два разных подхода. Идея паттерна mediator в контроле коммуникаций внутри себя. Т.е. реализация отвечает как за подписку, так и за публикацию. А вот в случае с pub-sub мы как раз уходим от централизованного управления. Мне нравится понимать это, как работу с msmq. Из общего между pub-sub и mediator — тип решаемой задачи и отсутсвие прямых ссылок.