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

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

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

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

Перечень перечисленных ограничений не является полным, однако указанные ограничения являются наиболее существенными. В данной статье предлагается реализация более сложной событийной модели, которая позволит снять все перечисленные ограничения.
При разработке программного обеспечения с использованием данной модели обработки событий последовательность этапов обработки следующая:
  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 или воспользоваться шаблонами.

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

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 18

    +2
    что-то мне кажется у вас неправильно с архитектурой раз вам это понадобилось

    может привести пример «на кошках»?
      0
      Например, вывод основного дизайна сайта, когда подключаются все имеющиеся модули, в «первом круге» каждый модуль анализирует текущую ситуацию и отправляет заявку на «второй круг», где выигравший модуль и генерирует дизайн
        0
        Конкретно эта задача возникала у меня не раз и всегда получалось решить ее очень просто. В частности, в Django, где используется некое подобие MVC, эту логику можно разместить во вьюхе и даже в зависимости от ситуации передавать данные в разные шаблоны.

        Зачем так усложнять я тоже понять не могу. Может быть дадите более расширенный пример?
          0
          Шаблоны динамически подключаемые, о конкретном кол-ве и логике вам не известно, а так шаблон подключается на событие и сам устанавливает приоритет вывода, на самом деле можно много где применить данную модель да и реализуется она достаточно быстро. Вывод дизайна, обработка шаблонных операторов, простые события (добавление пользователя, добавление комментария)…
            0
            Вернее модули, конечно же модули =)
              0
              Я бы сакцентировал побольше внимания на независимости отдельных обработчиков (то есть, как я понял, на том, что они могут быть частью совершенно разных модулей) и на том, что все это затевается ради приоритезации. Как-то из статьи это не сразу становится понятным, или я что-то упускаю.

              Другой вопрос, что я ни разу не сталкивался с такой необходимостью. Поэтому моя первая реакция была та же, что и у @akaBd, то есть подумалось, что что-то не так с дизайном системы.
                0
                Спасибо, учту в следующих работах. Все приходит с опытом, статьи тоже не просто писать =)
        +2
        еще раз перечитал… не, без 100 гр че-то не могу понять никак идею…
        я 1 сегодня работаю?
          0
          Идея в том, что данную модель можно использовать как стандартную модель обработки событий, однако, там где это необходимо, можно выбирать результирующий обработчик (обработчик «второго круга») путем назначения приоритетов от каждого обработчика («первого круга»)
          0
          это просто цепочки вызововов с приоритетами? так?
            0
            В статье под цепочкой вызовов я понимал возможность писать код, как в jquery $a->start()->right()->step(2)->stop();
            А в общем все действительно просто, назначается приоритет, кто даст больше тот и выиграл, просто как то реализовал в своем проекте данную модель, понравилась, решил поделиться, мб кому нибудь тоже пригодится
            0
            «обработчики анализируя состояние системы выдВигают приоритеты»
            Конец статьи, начиная со схемы, действительно сложен для понимания с первого раза. Особенно без конкретного примера.
            По ходу чтения возникло пару вопросов:
            1. Как поведёт себя система при большом количестве слушателей на одно событие? Я так понимаю, что Вы выводите модульный дизайн с помощью такой хитрой схемы, соответственно одним обработчиком его не вывести, т.е Вы скорее всего используете рекурсивную генерацию событий для каждой части сайта (по крайней мери это было бы логично, раз хочется добиться такой динамики и независимости). Если на каждое событие установлено >10-20 слушателей, и каждому из них даётся время на анализ входящих данных, базовые манипуляции и вынесение решения, то не будет ли такая система «тормозить» при разработке серьёзных и высоконагруженных проектов?
            2. При работе слушателей первого круга, не затрагиваются ли какие-либо глобальные параметры системы? Может я просто не до конца проникся сутью, т.к детального примера работы не было, но манипуляции с данными, которые «возможно будут», а «возможно не будут» приняты в дальнейшем необходимо производить внутри какого либо контекста, либо иметь изолированную песочницу, чтобы можно было откатить изменения сделанные предыдущим слушателем первого круга. Возможно это и не нужно, если нет работы с глобальными параметрами системы.
              0
              Спасибо за исправление,
              1) В том то и преимущество, что каждый обработчик разделен на две ветки: первый слушатель (именно он анализирует сложившуюся ситуацию и выдвигает приоритет обработки) и второй слушатель (в нем заключен основной функционал обработки события, например в нем генерируется дизайн для страницы). Логично реализовать обработчик «первого круга» как можно легче и все необходимые действия по обработке вынести в обработчик «второго круга». Если данный обработчик не выиграет приоритет на обработку события, то обработчик «второго круга» не выполняется вообще.
              Ну и конечно все это надо обрабатывать лишь при изменении внутреннего состояния, а в общем нужно все кешировать в nosql бд
              2) В принципе при возникновении событий, где требуется определить главный обработчик, обработчики «первого круга» будут лишь анализировать текущее состояние (в том числе и глобальные переменные), но не вносить какие либо изменения, эту роль может выполнить главный обработчик
              0
              Спасибо за статью, как-раз этот вопрос актуален (лично для меня), есть над чем поразмыслить :)

              При разработке больших приложений/систем пришел к выводу: подход описанный автором, в объемном проекте, намного проще поддерживать.

              В одном из последних проектов решил в качестве ядра системы сделать обработчик событий с слушателями и загрузчик модулей, для модулей.
              По ходу разработки проекта модули простым образом «вешаются» на ядро и в одну строчку подключаются к системе.

              И того:
              — единожды разработанное ядро можно применять в разных будущих разработках, подключая/отключая модули
              — программист работает только с модулем, требования к разработке модуля можно описать интерфейсом, в результате работа с самым обычным РНР + знать название и функции нескольких методов (важно для поиска новых кадров)
              — каждый модуль независимый, но может обращаться к другим модулям через ядро

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

              Городить этажные наследования и классы с десятками методов в тысячи строк кода сегодня не круто.
              0
              Прямой вызов обработчиков на событие практичен в простых задачах. Если задача чуть более сложна, то важными факторами правильности работы системы оказывается упорядоченность обработчиков. Приоритеты, порядковые номера – камень в модульность системы. Чтобы корректно добавить обработчик, нужно знать о других обработчиках. Правильным решением будет, когда ни обработчик, ни событие не влияют на приоритеты, а процесс обработки построен по цепочке обязанностей – вверх или вниз по иерархии компонентов системы. В Javascript, к примеру, событие нажатия по ссылке движется от самой ссылки по её родителям до документа. В итоге порядок обработки четко соблюдается структурой приложения.
                0
                Не совсем понял зачем 2 круга обработки событий и как это работает.

                Я когда события делал я все события разделил как во многих cms на 3 типа — событие, фильтр, действие (деление очень условное, это все событие). Событие просто кидает сообщение, обработчики его обрабатывают (каждый следующий обработчик получает результат предыдущего). Фильтр дает на вход значение, обработчики его «фильтруют» и возвращают на выходе отфильтрованное значение. Действие, как и событие, кидает событие, но оно ждет что в итоге что-то вернется инициатору события (событие с присвоением).

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

                Only users with full accounts can post comments. Log in, please.