Многоуровневая модель обработки событий

в 9:25, , рубрики: php, Алгоритмы, Песочница, события, метки: , ,

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

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

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

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

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

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

  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 или воспользоваться шаблонами.

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

Автор: krs

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js