Все дело в слабой взаимосвязи и единственной ответственности, которые идут рука об руку с шаблонами MV * (MVC / MVP / MVVM) в JavaScript, которые стали очень современными в последние несколько лет.
Слабая связь - это объектно-ориентированный принцип, в котором каждый компонент системы знает свою ответственность и не заботится о других компонентах (или, по крайней мере, старается не заботиться о них в максимально возможной степени). Слабая связь - это хорошо, потому что вы можете легко повторно использовать разные модули. Вы не связаны с интерфейсами других модулей. Используя публикацию / подписку, вы связаны только с интерфейсом публикации / подписки, что не имеет большого значения - всего два метода. Поэтому, если вы решите повторно использовать модуль в другом проекте, вы можете просто скопировать и вставить его, и он, вероятно, сработает или, по крайней мере, вам не потребуется много усилий, чтобы заставить его работать.
Говоря о слабой связи, следует упомянуть разделение проблем.. Если вы создаете приложение с использованием архитектурного шаблона MV *, у вас всегда есть Модель (и) и Представление (и). Модель - это бизнес-часть приложения. Вы можете повторно использовать его в разных приложениях, поэтому не рекомендуется объединять его с представлением одного приложения, где вы хотите его показать, потому что обычно в разных приложениях у вас разные представления. Так что неплохо использовать публикацию / подписку для коммуникации Model-View. Когда ваша Модель изменяется, она публикует событие, View улавливает его и обновляет себя. У вас нет накладных расходов на публикацию / подписку, это помогает вам в разделении. Таким же образом вы можете сохранить логику приложения, например, в Контроллере (MVVM, MVP, это не совсем Контроллер) и сохранить представление как можно более простым. Когда ваше представление изменяется (или пользователь нажимает что-то, например), он просто публикует новое событие, контроллер улавливает его и решает, что делать. Если вы знакомы сШаблон MVC или с MVVM в технологиях Microsoft (WPF / Silverlight) вы можете думать о публикации / подписке как о шаблоне Observer . Этот подход используется в таких фреймворках, как Backbone.js, Knockout.js (MVVM).
Вот пример:
//Model
function Book(name, isbn) {
this.name = name;
this.isbn = isbn;
}
function BookCollection(books) {
this.books = books;
}
BookCollection.prototype.addBook = function (book) {
this.books.push(book);
$.publish('book-added', book);
return book;
}
BookCollection.prototype.removeBook = function (book) {
var removed;
if (typeof book === 'number') {
removed = this.books.splice(book, 1);
}
for (var i = 0; i < this.books.length; i += 1) {
if (this.books[i] === book) {
removed = this.books.splice(i, 1);
}
}
$.publish('book-removed', removed);
return removed;
}
//View
var BookListView = (function () {
function removeBook(book) {
$('#' + book.isbn).remove();
}
function addBook(book) {
$('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>');
}
return {
init: function () {
$.subscribe('book-removed', removeBook);
$.subscribe('book-aded', addBook);
}
}
}());
Другой пример. Если вам не нравится подход MV *, вы можете использовать что-то немного другое (есть пересечение между тем, что я опишу ниже, и последним упомянутым). Просто разделите свое приложение на разные модули. Например, посмотрите Twitter.
Если вы посмотрите на интерфейс, у вас просто разные коробки. Вы можете рассматривать каждую коробку как отдельный модуль. Например, вы можете опубликовать твит. Это действие требует обновления нескольких модулей. Во-первых, он должен обновить данные вашего профиля (верхнее левое поле), но он также должен обновить вашу временную шкалу. Конечно, вы можете сохранять ссылки на оба модуля и обновлять их отдельно, используя их общедоступный интерфейс, но проще (и лучше) просто опубликовать событие. Это упростит модификацию вашего приложения из-за более слабой связи. Если вы разрабатываете новый модуль, который зависит от новых твитов, вы можете просто подписаться на событие «publish-tweet» и обработать его. Такой подход очень полезен и может сильно изолировать ваше приложение. Вы можете очень легко повторно использовать свои модули.
Вот базовый пример последнего подхода (это не оригинальный твиттер-код, это просто мой образец):
var Twitter.Timeline = (function () {
var tweets = [];
function publishTweet(tweet) {
tweets.push(tweet);
//publishing the tweet
};
return {
init: function () {
$.subscribe('tweet-posted', function (data) {
publishTweet(data);
});
}
};
}());
var Twitter.TweetPoster = (function () {
return {
init: function () {
$('#postTweet').bind('click', function () {
var tweet = $('#tweetInput').val();
$.publish('tweet-posted', tweet);
});
}
};
}());
Об этом подходе есть отличный доклад Николаса Закаса . Что касается подхода MV *, то лучшие статьи и книги, которые я знаю, опубликованы Адди Османи .
Недостатки: вы должны быть осторожны с чрезмерным использованием публикации / подписки. Если у вас есть сотни событий, управление всеми ими может стать очень запутанным. У вас также могут быть конфликты, если вы не используете пространство имен (или используете его неправильно). Расширенная реализация Mediator, которая очень похожа на публикацию / подписку, может быть найдена здесь https://github.com/ajacksified/Mediator.js . У него есть пространство имен и такие функции, как «всплытие» событий, которые, конечно, можно прервать. Еще один недостаток публикации / подписки - жесткое модульное тестирование, может стать трудным изолировать различные функции в модулях и тестировать их независимо.
Основная цель - уменьшить взаимосвязь между кодом. Это в некоторой степени основанный на событиях способ мышления, но «события» не привязаны к конкретному объекту.
Ниже я напишу большой пример в некотором псевдокоде, который немного похож на JavaScript.
Допустим, у нас есть класс Radio и класс Relay:
Когда радио принимает сигнал, нам нужно, чтобы несколько реле каким-то образом ретранслировали сообщение. Количество и типы реле могут отличаться. Мы могли бы сделать это так:
Это прекрасно работает. Но теперь представьте, что мы хотим, чтобы другой компонент также принимал часть сигналов, которые получает класс Radio, а именно Speakers:
(извините, если аналогии не на высшем уровне ...)
Мы можем повторить шаблон еще раз:
Мы могли бы сделать это еще лучше, создав интерфейс, такой как «SignalListener», так что нам нужен только один список в классе Radio, и мы всегда можем вызывать одну и ту же функцию для любого объекта, который у нас есть, который хочет прослушивать сигнал. Но это по-прежнему создает связь между любым интерфейсом / базовым классом / и т.д., который мы выберем, и классом Radio. Обычно всякий раз, когда вы меняете один из классов Radio, Signal или Relay, вы должны думать о том, как это может повлиять на два других класса.
А теперь попробуем другое. Создадим четвертый класс с именем RadioMast:
Теперь у нас есть шаблон, о котором мы знаем, и мы можем использовать его для любого количества и типов классов, если они:
Итак, мы меняем класс Radio на его окончательную простую форму:
И мы добавляем динамики и реле в список приемников RadioMast для этого типа сигнала:
Теперь класс Speakers and Relay ничего не знает, кроме того, что у них есть метод, который может принимать сигнал, а класс Radio, будучи издателем, знает о RadioMast, для которого он публикует сигналы. Это суть использования системы передачи сообщений, такой как публикация / подписка.
источник
class
ключевого слова. Пожалуйста, подчеркните этот факт, например. путем классификации вашего кода как псевдокода.Другие ответы отлично показали, как работает шаблон. Я хотел ответить на подразумеваемый вопрос « что не так в старом методе? », Поскольку недавно работал с этим шаблоном, и я обнаружил, что это связано с изменением моего мышления.
Представьте, что мы подписались на экономический бюллетень. В бюллетене публикуется заголовок: « Понизить Dow Jones на 200 пунктов ». Это было бы странным и несколько безответственным посланием. Если, однако, было опубликовано: « Сегодня утром Enron подала заявление о защите от банкротства по главе 11 », то это более полезное сообщение. Обратите внимание, что это сообщение может привести к падению Dow Jones на 200 пунктов, но это другой вопрос.
Есть разница между отправкой команды и сообщением о том, что только что произошло. Имея это в виду, возьмите свою исходную версию шаблона pub / sub, пока не обращайте внимания на обработчик:
Здесь уже существует подразумеваемая сильная связь между действием пользователя (щелчок) и ответом системы (удаление приказа). Эффективно в вашем примере действие дает команду. Рассмотрим эту версию:
Теперь обработчик реагирует на что-то интересное, что произошло, но не обязан удалять заказ. Фактически, обработчик может делать все, что не связано напрямую с удалением заказа, но все же может иметь отношение к вызывающему действию. Например:
Различие между командой и уведомлением - полезное различие, которое следует проводить с помощью этого шаблона, IMO.
источник
remindUserToFloss
&increaseProgrammerBrowniePoints
) были расположены в отдельных модулях, вы бы опубликовали 2 события одно сразу после друг друга прямо здесь,handleRemoveOrderRequest
или вы быflossModule
опубликовали событие вbrowniePoints
модуле, когда этоremindUserToFloss()
будет сделано?Чтобы вам не приходилось жестко кодировать вызовы методов / функций, вы просто публикуете событие, не заботясь о том, кто его слушает. Это делает издателя независимым от подписчика, уменьшая зависимость (или связь, какой бы термин вы ни выбрали) между двумя разными частями приложения.
Вот некоторые недостатки связи, упомянутые в википедии
Представьте что-то вроде объекта, инкапсулирующего бизнес-данные. Он имеет жестко закодированный вызов метода для обновления страницы всякий раз, когда установлен возраст:
Теперь я не могу протестировать объект person без включения
showAge
функции. Кроме того, если мне нужно показать возраст в каком-либо другом модуле графического интерфейса, мне нужно жестко закодировать этот вызов метода.setAge
, и теперь в объекте человека есть зависимости для двух несвязанных модулей. Также просто трудно поддерживать, когда вы видите, что эти вызовы выполняются, а они даже не находятся в одном файле.Обратите внимание, что внутри того же модуля вы, конечно, можете иметь прямые вызовы методов. Но бизнес-данные и поверхностное поведение графического интерфейса не должны находиться в одном модуле по любым разумным стандартам.
источник
removeOrder
вообще существует, поэтому вы не можете зависеть от него. Во втором примере вы должны знать.Реализация PubSub обычно видна там, где есть -
Пример кода -
источник
Документ «Многоликая публикация / подписка» - это хорошее чтение, и они подчеркивают одну вещь, которую они подчеркивают, - это разделение на три «измерения». Вот мое грубое резюме, но, пожалуйста, также обратитесь к статье.
источник
Простой ответ Первоначальный вопрос требовал простого ответа. Вот моя попытка.
Javascript не предоставляет никакого механизма для объектов кода для создания собственных событий. Итак, вам нужен некий механизм событий. шаблон публикации / подписки удовлетворит эту потребность, и вы должны выбрать механизм, который лучше всего соответствует вашим потребностям.
Теперь мы видим необходимость в шаблоне pub / sub, тогда вы бы предпочли обрабатывать события DOM иначе, чем то, как вы обрабатываете события pub / sub? Ради уменьшения сложности и других концепций, таких как разделение ответственности (SoC), вы можете увидеть преимущества единообразия всего.
Как это ни парадоксально, но больший объем кода способствует лучшему разделению проблем, что хорошо масштабируется до очень сложных веб-страниц.
Я надеюсь, что кто-то сочтет это обсуждение достаточно хорошим, не вдаваясь в подробности.
источник