Событие в объектно-ориентированное программировании (ООП) — это сообщение, которое возникает в различных точках исполняемого кода при выполнении определённых условий. Данные сообщения направляются обработчикам (слушателям), что позволяет своевременно реагировать на изменившееся состояние системы.

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

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

Перечень перечисленных ограничений не является полным, однако указанные ограничения являются наиболее существенными. В данной статье предлагается реализация более сложной событийной модели, которая позволит снять все перечисленные ограничения.
При разработке программного обеспечения с использованием данной модели обработки событий последовательность этапов обработки следующая:
  1. Необходимые слушатели подключаются на обработку своих событий.
  2. В программном коде инициируется событие.
  3. Выполняется «первый круг» обработки, всем слушателям подключившимся на обработку данного события предоставляется время на первичную обработку, в процессе которой каждый слушатель может получить собственные данные, данные от инициатора события, а также установить какие-либо данные для передачи во «второй круг» и подать заявку на обработку события во «втором круге».
  4. Среди всех слушателей данного события выбирается слушатель, который подал заявку с наибольшим приоритетом обработки, именно ему предоставляется право на обработку события во «втором круге». В процессе данной обработки он может получить данные от инициатора, собственные данные, данные от собственн��го обработчика «первого круга». После завершения обработки он может вернуть данные непосредственно инициатору события.

Рассмотрим преимущества данной модели:
  • Возможно «навешивать» один и тот же обработчик на разные события или даже на одно и тоже, но с разными собственными данными.
  • Возможно обрабатывать наиболее широкий круг событий. Например, добавление нового пользователя, вывод дизайна страницы сайта и т.д. В данном случаи событие добавления нового пользователя можно обработать используя лишь «первый круг», а событие вывода дизайна страницы сайта необходимо обрабатывать в «два круга».
  • Выполняется лишь один обработчик «второго круга» (Рассмотрим событие вывода дизайна сайта: на первом круге обработчики анализируя состояние системы выдвигают приоритеты обработки события и лишь на «втором круге» один из них генерирует дизайн страницы сайта).


На картинке представлена схема реализации данной модели. Эта реализация изначально опирается на работу с использованием цепочки вызовов. Ниже представлен пример реализации модели на языке PHP.
  • Events – основной класс. Лишь его объект пользователь может создать сам. Данный класс предоставляет возможность инициировать события и «навешивать» обработчики событий.
    • newListener – позволяет добавить нового слушателя и тут же «настроить» его (добавление происходит через адаптер для Events_Listener – Events_Listener_adapterSET).
    • newEvent – позволяет сконфигурировать новое событие, а затем инициировать его (данное действие происходит через адаптер для Events_Fire – Events_Fire_adapterSET).

    К процессу работы прозрачно для пользователя подключаются и другие классы:
    • Events_Fire – предоставляет общий интерфейс работы с событием, который ограничивают адаптеры;
    • Events_Fire1 – при вызове первого круга обработки. Данный класс позволяет получить все имеющиеся данные текущего состояния, добавить обработчик второго круга, а также установить его приоритет (через адаптер Events_Fire2_adapterSET);
    • Events_Fire2 – при вызове результрующего обработчика («второй круг»). Этот класс позволяет получить все имеющиеся данные, включая данные от обработчика «первого круга»;
    • Events_Listener — предоставляет общий интерфейс работы с обработчиком события. Методы данного класса также используют адаптеры Events_Fire1, Events_Fire2;
    • Events_Data – класс для хранения и передачи данных внутри событийной модели.


Рассмотрим пример работы данной модели:
class SampleListener
{
	public function Fire1(Event_fire1 $EF1)
	{
		$EF1->setFinal(Array($this,”Fire2”), $EF1->getListenerData()->sort);
	}
	public function Fire2(Event_fire2 $EF2)
	{
		return ($EF2->getListenerData()->sort() + $EF2->getFireData()->sort());
	}
}

$listener = new SampleListener();

$data1 = new Events_Data();
$data1->sort = 10;
Events::newListener("sampleModul", "sampleEvent", Array($listener, 'Fire1'))->setData($data1);

$data2 = new Events_Data();
$data2->sort = 20;
Events::newListener("sampleModul", "sampleEvent", Array($listener, 'Fire1'))->setData($data2);

$data3 = new Events_Data();
$data3->sort = 30;
Echo Events::newEvent("sampleModul", "sampleEvent")->setData($data3)->fire();

После инициализации события sampleEvent вызовется метод Fire1 слушателя $listener с первыми данными (sort=10, именно этот приоритет он и поставит на финальную обработку), а затем этот же метод вызовется с другими данными (sort=20, 20>10, следовательно слушатель с этими данными и получит право финальной обработки события). В заключении вызовется метод Fire2 (Обработчик тот же, но данные $data2). Данные от события (sort=30) складываются с данными от слушателя (sort=20). В итоге событие в ответ вернет нам число 50, которое и будет выведено на экран.

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

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

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

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