Программирование на основе событий: когда оно того стоит?

19

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

Я разрабатываю небольшое приложение. Это простое приложение, и по большей части его функциональность является базовой CRUD.

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

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

Каков наилучший подход в этом случае?

ABL
источник
2
Я бы сказал, не реализовывайте ничего самостоятельно - просто используйте существующую шину событий. Это сделает жизнь намного проще ...
Борис Паук
Программирование, управляемое событиями, изначально асинхронно. События могут проходить, а могут и не проходить, в том порядке, в каком вы планировали, или, может быть, в другом порядке или нет. Если вы можете справиться с этой дополнительной сложностью, сделайте это.
Питер Б
Управляемые событиями обычно означают, что ваш код предоставляется как обратные вызовы, и они вызываются из других мест способами, которые вы не можете предсказать. Ваше описание звучит так, когда в вашем коде происходит что-то конкретное, вам нужно сделать больше, чем требовала бы наивная реализация. Просто закодируйте дополнительные звонки.
Торбьерн Равн Андерсен
Там является разница между событием , управляемым и на основе событий. Посмотрите, например, вызывающий мысли эпизод подкаста .NET Rocks 355 с участием Теда Фэйсона. Тед Фэйсон доводит события до предела! ( прямая ссылка на скачивание ) и книга Программирование на основе событий: доведение событий до предела .
Питер Мортенсен
Интервью с Тедом Фэйсоном начинается в 13 минут 10 секунд.
Питер Мортенсен

Ответы:

31

Следуйте принципу KISS: будь проще, глупее или принципу YAGNI: он тебе не понадобится.

Вы можете написать код как:

void updateSpecialData() {
    // do the update.
    backupData();
}

Или вы можете написать код как:

void updateSpecialData() {
     // do the update.
     emit SpecialDataUpdated();
}

void SpecialDataUpdatedHandler() {
     backupData();
}

void configureEventHandlers() {
     connect(SpecialDataUpdate, SpecialDataUpdatedHandler);
}

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

События очень важны в правильной ситуации (представьте, что вы пытаетесь программировать пользовательский интерфейс без событий!) Но не используйте их, когда вместо этого вы можете использовать KISS или YAGNI.

Уинстон Эверт
источник
Мне особенно нравится тот факт, что вы упомянули, что срабатывание при изменении данных не является тривиальным.
NoChance
13

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

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

Подход, основанный на событиях, стоит вложений в более сложные сценарии, когда может происходить много разных взаимодействий, в контексте «многие ко многим», или если предусмотрены цепные реакции (например, субъект информирует наблюдателя, который в некоторых случаях хочет изменить предмет или другие предметы)

Christophe
источник
1
Я в замешательстве, не является ли наблюдатель одним из способов реализации событий?
svick
1
@ Свик, я так не думаю. В программировании, управляемом событиями, у вас есть основной цикл, который обрабатывает события в отношениях «многие ко многим» с разъединенными отправителями и наблюдателями. Я думаю, что наблюдатель может внести свой вклад, обрабатывая тип события в перкулярной области, но вы не можете достичь полного спектра EDP только с помощью наблюдателя. Я думаю, что путаница возникает из-за того, что в программном обеспечении, управляемом событиями, наблюдатели иногда реализуются на вершине обработки событий (обычно MVC с графическим интерфейсом)
Кристоф
5

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

События, возможно, являются одним из наиболее важных инструментов в ОО (согласно Алану Кейу - объекты связываются, отправляя и получая сообщения ). Если вы используете язык, который имеет встроенную поддержку для событий, или рассматриваете функции как первоклассных граждан, то использовать их не составит труда.

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

Стоит ли в маленьком приложении? Я бы сказал определенно да .

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

Если вы думаете: «О, но на самом деле это очень маленькое приложение, это не имеет большого значения» , подумайте:

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

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

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

Бен Коттрелл
источник
2

Шаблон наблюдателя может быть реализован гораздо меньшим образом, чем его описывает статья в Википедии (или книга GOF), при условии, что ваши языки программирования поддерживают что-то вроде «обратных вызовов» или «делегатов». Просто передайте метод обратного вызова в ваш код CRUD (метод наблюдателя, который может быть либо универсальным методом «записи в файл», либо пустым). Вместо «запуска события» просто вызовите этот обратный вызов.

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

Это принесет вам «лучшее из обоих миров», не жертвуя развязкой для «ЯГНИ».

Док Браун
источник
Это работает в первый раз, когда Док, но когда событие должно вызвать вторую вещь, вполне вероятно, что класс нужно будет модифицировать, делая это таким образом. Если вы используете шаблон наблюдателя, вы можете добавить столько новых поведений, сколько захотите, не открывая исходный класс для модификации.
RubberDuck
2
@RubberDuck: ОП искал решение, позволяющее «избежать ненужной сложности» - если требуются разные события / другое поведение, он, вероятно, не будет рассматривать шаблон наблюдателя как слишком сложный. Так что я согласен с тем, что вы сказали, когда дела станут более сложными, полный наблюдатель будет лучше служить ему, но только тогда.
Док Браун
Справедливое утверждение, но для меня это похоже на разбитое окно.
RubberDuck
2
@RubberDuck: добавление полного наблюдателя, с механикой издателя / подписчика «на всякий случай», когда это не очень нужно, кажется мне сверхнормативным - это не лучше.
Док Браун
1
Я не согласен, что это может быть из-за инженерии. Я, вероятно, чувствую то же, что и я, потому что это тривиально реализовать в моем избранном стеке. В любом случае, мы просто согласимся не согласиться?
RubberDuck