|
 |
Путь: >
Общие вопросы
Общие вопросы
Автор: - KievMan
Дата публикации - 06.10.2005
Просмотров: - 9011
Событийно-ориентированные приложения в PHP
Аудитория: Эта статья расcчитана на программистов знакомых с объектно-ориентированным проектированием и программированием, отличающих алгоритмическую декомпозицию от объектной, понимающих роль абстракции в проектировании, знающих достоинства, недостатки и область применения процедурного и объектного подхода. Обо всём этом можно узнать из замечательной книги "Объектно-ориентированный анализ и проектирование" [1], которую рекомендуется прочитать для лучшего понимания данной статьи.
[p]События - зачем они нужны?[/p]
Возможно, вам не приходилось задумываться над этим вопросом, и вы принимали события как необходимость языка программирования (например, в JavaScript) или как атрибут языка, не имеющий особого значения. Сейчас самое время задуматься над значением событий, ведь в статье будет пропагандироваться использование событий в PHP.
С каждым днём перед программистами возникают всё более сложные задачи. Как справиться с нарастающей сложностью? Гради Буч (Grady Booch), в своей книге [1], предлагает использовать декомпозицию, выделение абстракций и создание иерархий. В той же книге обсуждаются несколько методов выделения абстракций, в каждом из которых выделяется такая абстракция как события. Итак, события это прежде всего абстракция, которая помогает справиться с нарастающей сложностью системы, упростить её понимание, реализацию и модификацию.
[p]События в PHP - нужны ли они?[/p]
Во многих других языках программирования существует встроенная поддержка событий. Возможно, в PHP она не нужна, раз её нет?
В последнее время [url=http://phpinside.ru/?q=node/136]PHP пробирается в корпоративный сектор[/url], а это означает, что скоро перед PHP программистами будут возникать сложные бизнес задачи. Чтобы справиться с такими задачами, не обойтись без накопленного опыта ОО проектирования, частью которого является такая абстракция как события. Итак, PHP готов для бизнес проектов! А программисты?.. Чтобы не ждать пока грянет гром, лучше уже сейчас начать подготовку к будущим свершениям.
[p]Возможны ли события в PHP?[/p]
В объектно-ориентированном проектировании взаимодействие объектов рассматривается как обмен сообщениями или событиями. Сам механизм передачи событий никак не оговаривается, и в с[i]а[/i]мом простом случае это может быть обыкновенный вызов метода объекта. В таком случае, можно сказать что в PHP уже есть события? Не совсем, точнее сказать, есть простейший механизм передачи событий, а события - это просто абстракция, которая существует независимо от языка. Сейчас вполне уместно перейти к примеру.
Предположим, что в приложении есть объект Button, который реагирует на нажатие кнопки пользователем (как именно это происходит будет сказано позже). Далее, при нажатии на кнопку должно отправляться сообщение. Запишем это на PHP*:
[php]
<?
class Message
{
public function send()
{
//отправляет сообщение
}
}
class Button
{
protected function onClick()
{
$this->message->send();
}
}
?>
[
/php]
Где здесь найти абстракцию, именуемую событием? Чтобы проще было понять, можно привести следующую аналогию: метод send() является входным интерфейсом объекта Message, а метод onClick() вполне можно назвать [i]выходным интерфейсом[/i] объекта Button, или [i]событием[/i]. В самом деле, события обладают теми же свойствами что и интерфейс. Такой привычный всем интерфейс - это, по сути, абстракция, которая выделяет взаимодействие между объектами, служит границей между абстракциями. Объекту, реализующему интерфейс, неизвестно и не интересно кто и как будет использовать его интерфейс. Точно также события являются внешней границей объекта, которому не интересно кто и как будет их обрабатывать.
Теперь посмотрим, насколько пригоден такой механизм передачи события. Логично предположить, что в приложении будет не одна кнопка, а несколько, поэтому, возникает желание повторного использования кода. Тогда начинают сказываться недостатки такого механизма.
Основной недостаток - это жёсткая связь между объектами Button и Message, которая задаётся в определении класса. Поэтому, когда возникнет необходимость подключить к событию кнопки другой объект, или даже несколько, то придётся модифицировать метод onClick() в определении класса, причём для каждой кнопки в приложении. Значит, возникает задача разработать механизм передачи событий, в котором связь между объектами задавалась бы не в определении класса, а в самом приложении.
Например:
[php]
<?
class Button
{
private $onClick = array();
public function addEventHandler($handler, $eventType)
{
array_push($this->$eventType, $handler);
}
public function fireEvent($eventType)
{
foreach ($this->$eventType as $handler) {
call_user_func($handler);
}
}
}
$msg = new Message;
$btn = new Button;
$btn->addEventHandler(array($msg, 'send'), 'onClick');
?>
[
/php]
Как видно из примера, в классе Button добавился метод [b]addEventHandler()[/b], который позволяет назначать обработчики событий для объекта Button, причём несколько обработчиков на одно событие. Информация о назначенных обработчиках сохраняется в массиве [b]$onClick[/b]. Для добавления нового события в объект, достаточно добавить в определение класса новый массив с именем события. Теперь связи между объектами задаются вне определения класса, что положительно сказывается на структурированности кода приложения. Вместо метода [b]onClick()[/b] появился метод [b]fireEvent()[/b], который вызывает обработчики, назначенные для указанного события.
Этот механизм передачи событий полностью удовлетворяет текущим потребностям. Возникает вопрос, а где же область применения первого, самого простого механизма, он что же, уже не нужен? Нужен. Ведь второй механизм разрабатывался для событий, которые могут использоваться повторно. При его реализации, так же как и при реализации любой абстракции, был написан вспомогательный код, снижающий производительность приложения. Поэтому там, где события не представляют интереса для повторного использования, целесообразно использовать самый простой механизм передачи событий, т.е. обыкновенный вызов метода объекта.
Итак, модель событий для объектов определена. Но это ещё не всё. Ведь в приложении, в целом, тоже могут быть события. Подойдёт ли эта модель событий для приложения?
Сразу нужно отметить, что между объектом и приложением есть определённые различия, которые сказываются на проектных решениях. Объект находится на более низком уровне абстракции, т.е. функциональность объекта является более общей, предполагается неизменной (от приложения к приложению) и задаётся в определении класса. Используемый для объектов механизм передачи событий обладает достаточной гибкостью при назначении обработчиков событий, но сами события жёстко заданы в определении класса.
Приложение, в свою очередь, разрабатывается под узкие задачи, т.е. приложение находиться на более высоком уровне абстракции. Функциональность приложения полностью определяется набором объектов, входящих в приложение, и взаимодействием между ними, т.е. потоком событий. События приложения - это события входящих в него объектов, т.е. набор объектов приложения определяет набор событий приложения. Поэтому, для приложения желательна возможность управления объектами приложения, а также, возможность управления потоком событий.
Существует множество моделей событий используемых в других языках. Не вдаваясь в подробности поиска решения, сразу перейдём к самому решению. Как указывает Гради Буч (Grady Booch), в своей книге [1], всякое приложение представляет собой иерархию объектов с различными уровнями абстракции. Таким образом, наиболее подходящей структурой приложения будет древовидная структура объектов. Подобные
структуры можно встретить в DOM [2] или в Delphi. В том же DOM [3] или Delphi применяются модели событий ориентированные на древовидную структуру приложения. С некоторыми изменениями, подобная модель событий подойдёт и в нашем случае.
Итак, запрос к странице (массив $_REQUEST) рассматривается как поток событий, т.е. каждый элемент массива есть событие, означающее либо ввод значения в поле формы, либо выбор элемента списка, либо нажатие на кнопку, и т.д. в зависимости от контекста.
- Все события приложения помещаются в корневой объект приложения. Таким образом, обеспечивается единый источник событий. Как дополнительное преимущество, это позволяет организовать единую обработку исключений приложения, ведь все необработанные исключения будут перехвачены в корневом объекте.
- Корневой объект содержит очередь событий, ожидающих обработки. События помещаются в конец очереди, а извлекаются с начала, т.е. событие, помещённое первым, будет первым обработано.
- Каждый объект приложения может поместить событие в очередь событий. В ответ на внешние события, объекты могут создавать свои события, для взаимодействия с другими объектами.
- Распространение событий происходит от корневого объекта к дочерним объектам. Это позволяет родительски мобъектам управлять потоком событий, идущим к дочерним объектам.
На PHP это будет выглядеть так:
[php]
<?
/**
* Этот класс поддерживает структуру дерева приложения. Каждый созданный объект должен
* быть помещён в дерево как дочерний объект другого объекта. Также, в этом классе
* определены методы распространения событий по дереву приложения.
*/
abstract class Component
{
/**
* Массив ссылок всех дочерних объектов.
*/
protected $_components;
/**
* Распространяет события вниз по дереву.
*/
public function dispatchEvent($event)
{
foreach ($this->_components as $component) {
$component->handleEvent($event);
}
}
/**
* Обрабатывает события полученные от родительского объекта.
*/
public function handleEvent($event)
{
// обработка события в объекте.
$method = $event->type;
if (method_exists($this, $method)) {
$this->$method($event);
}
// если событие не обработано объектом,
// то оно передаётся дочерним объектам.
if (!$event->isClear()) {
$this->dispatchEvent($event);
}
}
}
/**
* Объект этого класса является корневым объектом приложения.
* Именно с этого объекта начинается распространение событий.
*/
final class Application extends Component
{
/**
* Очередь событий приложения.
*/
private $eventsQueue = array();
/**
* Помещает событие в очередь.
*/
public function pushEvent($event)
{
array_push($this->eventsQueue, $event);
}
/**
* Обрабатывает запрос полученный приложением.
*/
public function handleRequest()
{
if (0 == sizeof($_GET) && 0 == sizeof($_POST)) {
$this->pushEvent(new Event(onDefaultPage, $this, null));
} else {
$_REQUEST += $_FILES;
foreach ($_REQUEST as $name => $value) {
$this->pushEvent(new Event(onRequest, $this, array($name => $value)));
}
}
}
/**
* Запускает обработку очереди сообщений.
* Именно с этого метода начинается работа приложения.
*/
public function run()
{
if ($event = array_shift($this->eventsQueue)) {
$this->handleEvent($event);
}
}
}
?>
[
/php]
Кроме того, модель предусматривает различные способы распространения событий. События могут быть направленными и широковещательными. Направленное событие распространяется по дереву до тех пор, пока оно не будет обработано. Если событие так и не было обработано, то генерируется исключение. Широковещательные события всегда распространяются по всему приложению. Проверка обработки события не производится. Направленные события можно, в таком случае, сравнить с паттерном проектирования [i]Command[/i] [4], а широковещательные с паттерном [i]Observer[/i] [5], соответственно, область применения этих событий описывается этими паттернами проектирования.
Есть, также, два способа обработки событий: синхронный и асинхронный. В случае синхронного события, его распространение начинается сразу, без помещения события в очередь. После обработки события, объект, пославший событие, получает управление обратно. Асинхронное событие помещается в очередь событий и контролируется уже самим приложением.
Таким образом, можно использовать четыре комбинации событий:
[li]синхронные-направленные;[/li]
[li]синхронные-широковещательные;[/li]
[li]асинхронные-направленные;[/li]
[li]асинхронные-широковещательные;[/li]
Комбинация различных типов событий позволяет реализовать практически любую задачу.
В результате, в этой модели имеется единый источник событий, и нет жёсткой привязки событий приложения. Каждый объект может послать любое событие, и если в приложении найдётся объект, который слушает данное событие, оно будет обработано. Как дополнительное преимущество, данная модель позволяет работать над одним приложением нескольким независимым разработчикам. Это достигается благодаря хорошей структурированности кода и меньшему числу необходимых договорённостей между разработчиками.
Итак, были рассмотрены три механизма передачи событий, определены их области применения, достоинства и недостатки. Далее предстоит научиться использовать события при решении реальных задач.
В заключение осталось сказать, что описанная модель событий нашла своё отражение (с некоторыми различиями) в проекте PHP_Application [6].
* Все приведенные в статье примеры являются отрывками исходного кода, предназначенные только для учебных целей.
[p]Ссылки[/p]
[li][1] Гради Буч (Grady Booch), [url=http://books.kulichki.net/data/c/c/]"Объектно-ориентированный анализ и проектирование"[/url][/li]
[li][2] Document Object Model (DOM) Level 3 Core Specification [url=http://www.w3.org/TR/DOM-Level-3-Core]http://www.w3.org/TR/DOM-Level-3-Core[/url][/li]
[li][3] Document Object Model (DOM) Level 3 Events Specification [url=http://www.w3.org/TR/DOM-Level-3-Events]http://www.w3.org/TR/DOM-Level-3-Events[/url] [/li]
[li][4] Паттерн проектирования [url=http://ooad.asf.ru/patterns/patterninfo.asp?id=25]Command[/url][/li]
[li][5] Паттерн проектирования [url=http://ooad.asf.ru/patterns/patterninfo.asp?id=28]Observer[/url][/li]
[li][6] Проект [url=http://www.anter.com.ua/PApple/]PHP_Application[/url] - событийно ориентированный фрэймворк.[/li]
Обсудить в ФОРУМе - комментариев ()
Путь: >
Общие вопросы
Если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
|
|