Когда я должен использовать программирование на основе событий?


65

Я передавал обратные вызовы или просто запускал функции из других функций в моих программах, чтобы все происходило после завершения задач. Когда что-то заканчивается, я запускаю функцию напрямую:

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    shovelSnow();
}

Но я читал о многих различных стратегиях в программировании, и та, которая, как я понимаю, является мощной, но еще не практиковалась, основана на событиях (я думаю, что метод, о котором я читал, назывался «pub-sub» ):

var ground = 'clean';

function shovelSnow(){
    console.log("Cleaning Snow");
    ground = 'clean';
}

function makeItSnow(){
    console.log("It's snowing");
    ground = 'snowy';
    $(document).trigger('snow');
}

$(document).bind('snow', shovelSnow);

Я хотел бы понять объективные сильные и слабые стороны программирования, основанного на событиях, против простого вызова всех ваших функций из других функций. В каких ситуациях программирования имеет смысл использовать программирование на основе событий?


2
В сторону, вы можете просто использовать $(document).bind('snow', shovelShow). Нет необходимости заключать его в анонимную функцию.
Карл Билефельдт

4
Вам также может быть интересно узнать о «реактивном программировании», которое имеет много общего с программированием на основе событий.
Эрик Липперт

Ответы:


75

Событие является уведомлением , описывающим возникновение недавнего прошлого.

Типичная реализация управляемой событиями системы использует функции диспетчера событий и обработчика (или подписчиков ). Диспетчер предоставляет API для привязки обработчиков к событиям (jQuery's bind) и метод публикации события своим подписчикам ( triggerв jQuery). Когда вы говорите о событиях ввода-вывода или пользовательского интерфейса, обычно также есть цикл обработки событий , который обнаруживает новые события, такие как щелчки мыши, и передает их диспетчеру. В JS-land диспетчер и цикл обработки событий обеспечиваются браузером.

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

В вашем примере, где вы вызываете событие в своем коде, а не вызываете функцию напрямую, есть несколько более интересных компромиссов, о которых я расскажу ниже. Основное отличие состоит в том, что издатель события ( makeItSnow) не указывает получателя вызова; это связано в другом месте (в вызове bindв вашем примере). Это называется « запусти и забудь» : makeItSnowобъявляет миру, что идет снег, но ему все равно, кто слушает, что происходит дальше, или когда это происходит - он просто передает сообщение и отряхивает его руки.


Таким образом, подход, управляемый событиями, разъединяет отправителя сообщения от получателя. Это дает вам одно преимущество: у данного события может быть несколько обработчиков. Вы можете привязать gritRoadsфункцию к вашему событию снега, не затрагивая существующий shovelSnowобработчик. У вас есть гибкость в том, как ваше приложение подключено; чтобы отключить поведение, вам просто нужно удалить bindвызов, а не искать код, чтобы найти все экземпляры поведения.

Еще одно преимущество программирования, управляемого событиями, заключается в том, что оно дает вам возможность заняться сквозными вопросами. Диспетчер событий играет роль посредника , а некоторые библиотеки (например, Brighter ) используют конвейер, чтобы можно было легко подключать общие требования, такие как ведение журнала или качество обслуживания.

Полное раскрытие: Brighter разработан в Huddle, где я работаю.

Третье преимущество отсоединения отправителя события от получателя заключается в том, что оно дает вам гибкость при обработке события. Вы можете обрабатывать события каждого типа в своем собственном потоке (если ваш диспетчер событий их поддерживает), или вы можете помещать инициированные события в посредник сообщений, такой как RabbitMQ, и обрабатывать их с помощью асинхронного процесса или даже обрабатывать их массово в одночасье. Получатель события может находиться в отдельном процессе или на отдельной машине. Вам не нужно менять код, который вызывает событие, чтобы сделать это! Это большая идея, лежащая в основе «микросервисных» архитектур: автономные сервисы обмениваются данными с использованием событий, а промежуточное ПО для обмена сообщениями является основой приложения.

Для довольно другого примера стиля, управляемого событиями, обратите внимание на дизайн, управляемый доменом , где события домена используются, чтобы помочь сохранить агрегаты отдельными. Например, рассмотрим интернет-магазин, который рекомендует товары на основе вашей истории покупок. A Customerнеобходимо обновить историю покупок при ShoppingCartоплате. ShoppingCartАгрегат может уведомить Customer, подняв CheckoutCompletedсобытие; Customerбудет обновляться в отдельной транзакции в ответ на событие.


Основным недостатком этой управляемой событиями модели является косвенность. Теперь труднее найти код, который обрабатывает событие, потому что вы не можете просто перейти к нему с помощью IDE; Вы должны выяснить, где событие связано в конфигурации, и надеяться, что вы нашли все обработчики. Есть больше вещей, чтобы держать в голове в любое время. Здесь могут помочь соглашения о стиле кода (например, размещение всех вызовов bindв одном файле). Ради вашего здравого смысла важно использовать только один диспетчер событий и использовать его последовательно.

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

В определенных обстоятельствах производительность может быть проблемой. При обработке сообщения диспетчер должен:

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

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


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


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

1
Является ли скорость существенным недостатком кода, управляемого событиями? Кажется, что это может быть, но я не совсем знаю.
raptortech97

1
@ raptortech97 это конечно может быть. Для кода, который должен быть особенно быстрым, вы, вероятно, захотите избежать отправки событий во внутреннем цикле; К счастью, в таких ситуациях обычно четко определено, что вам нужно сделать, поэтому вам не нужно сверхгибко событий (или публикация / подписка или наблюдатели, которые являются эквивалентными механизмами с другой терминологией).
Жюль

1
Также обратите внимание, что есть некоторые языки (например, Erlang), построенные вокруг модели актера, где все является сообщениями (событиями). В этом случае компилятор может решить, реализовывать ли сообщения / события как прямые вызовы функций или как связь.
Брендан

1
Для «производительности» я думаю, что мы должны различать однопоточную производительность и масштабируемость. Сообщения / события могут быть хуже для однопоточной производительности (но могут быть преобразованы в вызовы функций за нулевую дополнительную стоимость и не хуже), а для масштабируемости они превосходят практически во всех отношениях (например, могут привести к значительным улучшениям производительности в современных многопользовательских системах). - ЦПУ и будущие «многопроцессорные» системы).
Брендан

25

Программирование на основе событий используется, когда программа не контролирует последовательность событий, которые она выполняет. Вместо этого поток программы направляется внешним процессом, таким как пользователь (например, GUI), другой системой (например, клиент / сервер) или другим процессом (например, RPC).

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

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

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


18

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

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


6

Я хотел бы добавить простую аналогию, которая помогла мне:

Думайте о компонентах (или объектах) вашего приложения как о большой группе друзей на Facebook.

Когда один из ваших друзей хочет что-то вам сказать, он может позвонить вам напрямую или опубликовать это на своей стене в Facebook. Когда они публикуют его в своем Facebook, тогда любой может увидеть это и отреагировать на это, но многие люди этого не делают. Иногда людям важно реагировать на это, например: «У нас есть ребенок!». или «Такая-то группа устраивает неожиданный концерт в баре Drunkin 'Clam!». В последнем случае остальным друзьям, вероятно, придется реагировать на это, особенно если они заинтересованы в этой группе.

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

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


0

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

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

Если одинокому парню требуется 3 минуты для заполнения, 10-му парню придется подождать до 30 минут. Теперь, чтобы сократить время ожидания этих 10-ых парней, решением будет увеличение числа администраторов, что является дорогостоящим. Это то, что происходит на традиционных веб-серверах. Если вы запрашиваете информацию о пользователе, последующий запрос других пользователей должен дождаться завершения текущей операции, извлекаемой из базы данных. Это увеличивает «время до ответа» 10-го запроса и увеличивается экспоненциально для n-го пользователя. Чтобы избежать этого, традиционные веб-серверы создают поток (эквивалентный увеличению числа регистраторов) для каждого отдельного запроса, т. Е. В основном он создает копию сервера для каждого запроса, что является дорогостоящим интервалом потребления ЦП, поскольку для каждого запроса потребуются операционные системы. нить. Чтобы масштабировать приложение,

Управляемый событиями . Другой подход для увеличения «времени отклика» очереди заключается в подходе, основанном на событиях, когда парень, находящийся в очереди, получит форму, попросит заполнить ее и вернуться по завершении. Следовательно, администратор всегда может принять запрос. Это именно то, что Javascript делает с самого начала. В браузере javascript будет реагировать на события щелчка пользователя, прокрутки, пролистывания или выборки из базы данных и так далее. Это возможно в javascript по своей сути, потому что javascript рассматривает функции как объекты первого класса, и они могут быть переданы в качестве параметров другим функциям (так называемые обратные вызовы) и могут быть вызваны при завершении конкретной задачи. Именно это и делает node.js на сервере. Вы можете найти больше информации о программировании, управляемом событиями и блокировке ввода / вывода, в контексте узла здесь

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.