Тема OS18асу. Понятие событийного программирования. Событийно-управляемое программирование - объектно-ориентированное программирование, при котором задаются реакции программы на различные события. Эволюция языков системного программирования asm->c->c++->java->python/jscript? Обзор сценариев. Процесс отладки. Использование объектов ActiveX. Апплеты Java (Агенты): Сигнал-Событие-Сообщение-Действие; Вариант событийного стиля: программирования от приоритетов Клиент-cервер и ООП; ООАнализ, ООДизайн и ООП-кодирование; понимание концепции событийно-управляемого программирования: переменные, циклические операторы, процедуры; объекты, свойства, методы и события. объект Event: Класс, Абстракция, Инкапсуляция, Наследование, Полиморфизм Событийное программирование предполагает наличие возможностей по управлению выполнением задач со стороны пользователя посредством возможностей операционной системы через события объектов. Перехват событий. Событие (прерывание) - это способность объекта реагировать на события, которые могут исходить от пользователя программы, и представляют собой программы. Программы обслуживания событий программируются в отличие от методов объектов теми, кто непосредственно использует эти объекты в своих программах. Пример 1. Наиболее часто используемым событием является событие Click. Это событие наступает, когда пользователь производит щелчок кнопкой мыши. Рассмотрим ситуацию, когда при щелчке мышью на форме Form1, необходимо изменить цвет фона формы по случайному закону. Пример 2. Можно организовать считывание координат курсора мыши и вывода их в активные элементы управления типа метка Label1 и Label2. Для решения этой задачи существует событие MouseMove. В таблице 1. приведены наиболее употребительные событий объектов Windows. Событие Назначение события Какие объекты имеют это событие Click Нажатие клавиши мыши Все объекты DlbClick Двойное нажатие клавиши мыши Все объекты Activate Загрузка объекта для выполнения UserForm Terminate Закрытие объекта UserForm MouseMove Передвижение указателя мыши по объекту Все объекты GotFocus Получения фокуса Все объекты KeyPress Отслеживание нажатия и отпускания клавиши Все объекты KeyDown Отслеживание нажатия клавиши Все объекты KeyUp Отслеживание отпускания клавиши Все объекты Любой активный элемент управления или объект современной операционной системы характеризуется большим числом свойств, методов и событий. Некоторые свойства, методы и события являются общими для всех объектов. Но у каждого типа элемента управления или типа объекта в Windows имеются свои специфичные свойства, методы и события. Чтобы обеспечить возможность внешней синхронизации процессов в фазе "пользователь" OS UNIX имитирует для каждого процесса систему программных прерываний, называемых сигналами. Сигнальный механизм позволяет процессу реагировать на различные события, моменты появления которых не могут быть заранее предусмотрены. Обработка сигналов позволяет реализовать популярную в настоящее время методологию событийного программирования. Событие, сообщение, демон Исторически этот стиль как определенный прием сформировался в области разработки операционных систем, где естественно связывать понятие события с прерываниями. Прерывание - это сигнал от одного из устройств (может быть, и от самого процессора), который говорит о том, что произошло нечто, на что следует обратить внимание. Когда происходит прерывание, операционная система распознает, какая причина его вызвала, и далее формирует событие как информационный объект, вызывающий реакцию программной системы. Возможны разные способы реагирования на события, в том числе и передача его для обработки той программе, при выполнении которой возникло прерывание, породившее это событие. Из потребности такой обработки, собственно говоря, и сформировался событийно-ориентированный стиль программирования. Наиболее очевидная область его адекватного применения - реализация интерактивных взаимодействий программы с пользователем и решение других подобных задач (например, тех, которые требуют опроса датчиков состояния каких-либо технологических процессов). В частности, при взаимодействии с пользователем чаще всего достаточно таких событий, как нажатие на клавишу, перемещение курсора мыши, указание световой кнопки и т. п. Не случайно именно те прерывания, которые способна перенаправить операционная система для обработки на уровень пользовательской программы, стали основой для выработки систем событий, реакция на которые задается в событийно-ориентированном стиле. События этого рода можно передать от одного обработчика к другому, не нарушая условий адекватного применения данного стиля: для этого достаточно объявить такое перенаправление события порождением нового события. Но требуется также генерация событий, не связанных с прерываниями. К примеру, программируя в событийно-ориентированном стиле, естественно объявить событием ситуацию, когда значение одной переменной становится больше другой. Однако такого рода свойства вычислительного процесса никак не отражаются в системе прерываний, а потому обычные языковые средства событийно-ориентированного программирования (например, в Delphi), часто провозглашаемые как универсальные, здесь не помогут. Необходимы иные механизмы, которые уводят программиста из области шаблонов проектов, стандартизующих обработку событий. Об этом обстоятельстве, ограничивающем применимость подхода, предпочитают умалчивать, и, как обычно бывает в подобных ситуациях, при беглом знакомстве с соответствующими средствами могут возникнуть ни на чем не основанные ожидания с последующими разочарованиями и даже отторжением стиля, хорошо работающего на своем месте. Примером языка, до некоторой степени ориентированного на событийно-ориентированное программирование, может служить Perl. Событийный стиль удобен не только на уровне конкретного программирования. Часто концептуальное осмысление задачи полезно проводить в терминах системы событий, а уж затем решать, как реализовывать систему: непосредственно использовать события как средство управления, моделировать событийное взаимодействие имеющимися средствами или вообще отказаться от этого механизма в данной разработке. В качестве иллюстрации этого тезиса опишем с позиций событийного стиля программирования взаимоотношения между синтаксическим анализом и вычислениями семантики программы. Реализация этих взаимоотношений - обычная задача, решаемая при разработке любого транслятора. В событийном описании этой задачи в качестве событий системы естественно рассматривать распознавание во входном потоке (транслируемом тексте) синтаксических единиц, то есть конструкций языка. Такое событие требует в качестве обработчика семантическую подпрограмму, связанную с распознаваемой конструкцией. Следовательно, выявлены два автономных процесса: синтаксический анализ, задающий генерацию событий, и семантическая обработка, осуществляемая как последовательность вызовов семантических подпрограмм-реакций, упорядоченная событиями. Коль скоро есть концептуальное разделение процессов, его можно воплотить в реальной системе в событийном стиле. Это решение влечет за собой следующее. Необходимость рассмотрения в качестве событий двух фактов: начала и завершения распознавания конструкции, поскольку именно с ними, а не с конструкцией в целом связываются семантические действия. Для обсуждаемой задачи существенно, что события возникают не в произвольном порядке, их множество имеет вполне определенную структуру. Не удивительно, что эта структура в точности соответствует синтаксической структуре текста: например, событие завершения распознавания конструкции не может возникать раньше, чем возникнет событие начала конструкции. Следствием этой структуры является понятие ожидания вполне определенных, а не произвольных событий. Если ожидание не оправдалось, то это можно считать событием еще одного вида: ошибочная ситуация. Количество таких событий в точности равно числу наборов ожидаемых событий. При событийном программировании разделение процессов генерации и обработки событий влечет за собой их полную независимость. В практике программирования транслирующих систем это качество считается не очень удобным, и вместо событийного механизма обычно используется схема сопрограммного взаимодействия с так называемым синтаксическим управлением: синтаксический анализатор сам вызывает нужные семантические программы, когда распознает необходимость сделать это (в событийном стиле это означает генерацию соответствующего события). Нет никакого противоречия между двумя схемами, и единственное различие между ними в том, что при синтаксическом управлении ожидание события оказалось возможным подменить прямым вызовом подпрограммы. Иными словами, в схеме трансляции удается статически, до выполнения программы вычислить события, наступление которых требует вызова их обработчиков (семантических подпрограмм). Вообще, сопрограммное взаимодействие можно трактовать как статически вычисленную событийность). Разумеется, рамки событийного программирования много шире тех случаев, когда можно статически определять необходимость активизации обработчика. Полезно объединение генерации событий с их обработкой, и за счет него можно растворять события в программе. В частности, и по этой причине следует рассматривать событийное программирование как самостоятельный стиль. Но интересны и такие случаи, когда объединение, хотя и возможно, но не делается. Конкретный пример - XML/XSL технология, для которой разделение структуры и интерпретации текста считается принципиальным: это позволяет строить съемные системы обработки, иметь несколько независимых таких систем для разного назначения. В своей сфере применения многовариантность имеет большие преимущества, но, как всегда, перенос принципов данной технологии куда угодно чреват уже не раз отмеченной неадекватностью. Программирование от приоритетов Обсуждая событийное программирование, мы упомянули о том, что при программировании в этом стиле может возникнуть потребность в упорядочивании выполнения нескольких конкурирующих между собой реакций на события. Достаточно универсальным средством удовлетворения этой потребности является приписывание реакциям приоритетов: сначала выполняются те реакции, которые имеют больший приоритет. Этот прием, как выявилось на практике, пригоден для решения достаточно широкого круга задач. Он стал основой для формирования самостоятельного варианта событийного стиля: программирования от приоритетов. Данный стиль порождена практическими потребностями. Она предназначена для организации работы многих взаимодействующих процессов, динамически порождаемых и исчезающих. Фундаментальных теоретических исследований в области программирования от приоритетов почти нет. В частности, в классической работе Хоара [29], в которой рассматривается управление взаимодействующими процессами, предполагается, что программа написана в традиционном структурном стиле и никаких попыток приспособить ее к обстановке, где имеется много процессов, нет2). Стиль программирования от приоритетов не реализован систематически в виде законченного языка (в качестве некоторой попытки можно привести проект Joule, см. http://www.agorics.com/Library/joule.html), но часто используется в прикладных языках скриптов для управления процессами или событиями либо в распределенных системах, либо на полуаппаратном уровне (так называемые встроенные программы, являющиеся частью специализированного прибора или устройства). В программировании от приоритетов, как и в сентенциальном программировании, порядок расположения операторов в программе не играет принципиальной роли, зато важен приоритет оператора, то есть некоторое значение, принадлежащее в самом общем случае частично-упорядоченному множеству и почти всегда рассматриваемое как элементарное. После завершения очередного оператора среди оставшихся выбирается оператор с максимальным приоритетом. Если таких операторов с равными или несравнимыми приоритетами несколько, то, вообще говоря, в идеале надо было бы выбирать один из них недетерминированно. Об операторах, имеющих приоритеты, можно говорить, когда программирование от приоритетов реализовано в специально предназначенном для этого стиля языке. Если же приходится моделировать программирование от приоритетов в обычном языке, то в качестве таких операторов могут выступать иные структурные единицы текста программы. В объектно-ориентированном языке, как правило, это методы объектов. При назначении приоритетов важно различать случаи, когда они задаются для операторов как элементов текста программы и когда приоритеты вводятся для динамических структурных единиц, то есть для тех элементов процесса вычислений, которые возникают в ходе выполнения программы, но связаны с тем или иным оператором, приоритет которого определяется в рамках конкретного экземпляра (например, объекта). Таким образом выстраиваются разные системы приоритетов, причем во втором случае система приоритетов вынужденно оказывается динамической. На практике обычно используют жесткую дисциплину, базирующуюся на порядке операторов в программе. Часто управление по приоритетам соединяется с так называемой системой демонов, то есть процедур, вызываемых при выполнении некоторого условия, а не в тот момент, когда при движении по тексту программы мы подошли к их явному вызову (сидит такой демон в засаде и выжидает момент, когда можно будет начать исполняться). И текущий <нормальный> процесс, и проснувшиеся демоны имеют свои приоритеты, и демон начнет исполняться, даже если он активизирован, лишь в том случае, если среди активных не осталось процесса с большим, чем у демона, приоритетом. Эта схема реализована, в частности, в системе UNIX. Уместно отметить, что событийно-ориентированное программирование порою может рассматриваться как вырожденный случай стиля программирования от приоритетов: для каждого экземпляра структурной единицы, способной реагировать на события, выставляется (бесконечно большой) приоритет, если этот экземпляр фактически должен активизировать реакцию, и не выставляется приоритет (выставляется бесконечно малый приоритет), если экземпляр не должен реагировать на событие. Общий случай стиля событийно-ориентированного программирования также близок к стилю программирования от приоритетов, если считать, что всем обработчикам события, которые должны быть выполнены при его появлении, присвоены приоритеты, соответствующие фактическому порядку их выполнения. Когда такой порядок неизвестен, можно считать, что всем активизируемым обработчикам присвоены равные приоритеты. Кардинальное различие между программированием от событий и от приоритетов состоит в принципах задания управления. В случае программирования от событий установлена прямая связь между событием и реакцией: если условие срабатывания открывает для обработчика возможность быть выполненным, то он обязательно будет выполнен в ответ на соответствующее событие. При программировании от приоритетов ситуация иная: задавая демону даже наивысший приоритет, можно лишь надеяться на то, что он сработает раньше других, причем, возможно, даже без появления какого бы то ни было события. Если абстрагироваться от механизма управления при оперировании с приоритетами и событиями, то можно считать эти два стиля вариантами одной сущности. Варианты событийного программирования от событий и приоритетов хорошо совмещаются, взаимно дополняя друг друга, когда целесообразна следующая архитектурная схема разработки программного проекта. От событий строятся фрагменты системы, которые отвечают за реакцию на каждое из событий, реализуемую разными обработчиками, а программирование этих обработчиков ведется от приоритетов. Рассмотрим написанный в двух вариантах на двух условных языках пример программы с приоритетами. 3: Цикл пока не будет все сделано { 5: Подготовка данных; 10: Обработка текущих изменений; } 4: Демон {Если Поступили данные То {Прием рутинных данных}; 8: Демон {Если Поступили экстренные данные То Запоминание экстренных изменений}; 12: Демон {Если Авария То Аварийные действия}; Пример 13.2.1. a: PRIO 3 {Prepare Data; SLEEP}; b: PRIO 10 {Process Changes; SLEEP}; c: PRIO 8 DAEMON IF Extra Data THEN Store Extra Data; AWAKE b FI; d: PRIO 12 DAEMON IF Alert THEN Emergency; FI; e: PRIO 2 DAEMON IF Idle THEN AWAKE a; FI; Пример 13.2.2. Видно, что в данном примере все приоритеты задавались статически. Именно к этому стремятся почти во всех случаях современной практики вычислений с приоритетами. Однако полной статичности можно добиться лишь в простейших случаях. Например, если процесс долго ждет своей очереди на исполнение, то естественно постепенно поднимать его приоритет. При этом приоритет такого <пользовательского> действия может превзойти приоритет системного процесса, и остается либо статически задавать каждому процессу допустимый максимальный приоритет, и мы вновь попадаем в ту же ловушку, либо задавать разным классам процессов приоритеты, просто не сравнимые по порядку, чтобы ни при каком нормальном исполнении приоритет пользовательского процесса не дорос, скажем, до 109, за которым начинаются приоритеты срочных системных демонов. Заметим, что назначение приоритетов операционной системой, <сверху>, принципиально ничего в данной картине не меняет. С точки зрения пользовательских программ эти приоритеты все равно статические. Рассмотрим теоретически, какие классы программ можно описать при помощи статических и динамических приоритетов. Оказывается, при помощи статических приоритетов, являющихся натуральными числами, невозможно описать даже примитивно-рекурсивные композиции исходных действий3). Таким образом, концептуально натуральных чисел недостаточно для описания приоритетов, но оказывается, что для них хватает ординалов (см. курс математической логики). Поскольку ординалы до ?0 имеют хорошую вычислительную модель, можно рассматривать целочисленные приоритеты как конкретную реализацию абстрактного класса таких ординалов. В случае реально распределенных или параллельных действий проблема приоритетов еще важнее и сложнее. Часто управление по приоритетам - единственный шанс справиться со сложностью такой нелинейной во времени системы. Тут уже и линейно упорядоченного множества приоритетов не хватает. В целом программирование от приоритетов является мощным, но специфическим орудием для описания глобальных совместных, параллельных или распределенных процессов. Его элементы проникают и в традиционные системы программирования в виде обработки исключительных ситуаций в программах. Здесь, как и в программировании от событий с обособленными реакциями, всего два приоритета, которые налагаются на структуру использующей конструкции. Противоречивость этого средства со структурным программированием только в том, что не всегда ясно, что делается далее, когда обработка исключения завершается. 1) Статическое вычисление некоторых частей программ - очень важный и имеющий в потенциале исключительно широкую область применения прием программирования. Если программист предусматривает, что его программа будет частично вычислена до основного выполнения программы, то он следует еще одному стилю - специализирующему программированию. 2) По этой причине область применения языка OCCAM, непосредственно реализующего идеи Хоара, оказалась неоправданно суженной до тех ситуаций, когда процессы жестко привязаны друг к другу статически описываемыми каналами передачи данных. 3) Правда, здесь мы отвлекаемся от реальных ограничений и считаем 1010000000000 столь же легко достижимым числом, как и 10. Понятия: событие (event), реакция (reaction), переменные (vars). В основу концепции управления объектами легла концепция "событийной управляемости" - объектами управляют события, с ними происходящие. Все очень просто - происходит какое-то событие и объект как-то на него реагирует. Собственно говоря, этот механизм мы наблюдаем в Windows: есть сообщения, очередь сообщений и, соответственно, обработчики сообщений. В нашем случае мы сами строим свой Windows - определяем сколько и какие сообщения будут у нас в движке и, соответственно, сколько и какие обработчики этих сообщений (естественно, у нас все это получится намного красивее и элегантее нежели в Windows ;-) События и обработчики событий являются очень важной частью для программирования. События, главным образом, инициируются теми или иными действиями пользователя. Если он щелкает по некоторой кнопке, происходит событие "Click". Если указатель мыши пересекает какую-либо ссылку гипертекста - происходит событие MouseOver. Существует несколько различных типов событий. Мы можем заставить нашу JavaScript-программу реагировать на некоторые из них. И это может быть выполнено с помощью специальных программ обработки событий. Так, в результате щелчка по кнопке может создаваться выпадающее окно. Это означает, что создание окна должно быть реакцией на событие щелка - Click. Программа - обработчик событий, которую мы должны использовать в данном случае, называется onClick. И она сообщает компьютеру, что нужно делать, если произойдет данное событие. Приведенный ниже код представляет простой пример программы обработки события onClick:
Модель событий в JavaScript 1.2: onEvent Abort Focus MouseOut Submit Blur KeyDown MouseOver Unload Click KeyPress MouseUp Change KeyUp Move DblClick Load Reset DragDrop MouseDown Resize Error MouseMove Select Событие (Event) - некоторое событие, произошедшее во внутренней системе движка. Примеры: ON_INIT - событие, вызываемое при инициализации объекта, ON_FRAME - событие, вызываемое каждый кадр, ON_HIT - при попадении в объект, ON_DESTROY - при уничтожении объекта и пр. То есть, можно определить сколь угодно много и любого рода события в своей системе. Так как объект может как-то реагировать на поступившее событие, то у нас появляется еще одно определение: - Реакция (Reaction) - определенное действие или набор действий, которые совершит объект при наступлении определенного события. Примеры: movDIRECTION - двигать объект в заданном направлении, setFOGCOLOR - изменить цвет тумана, getSPF - получить значение внутренней пременной движка, хранящей значение количества секунд в кадре, пр. Также очевидно, что количество реакций может быть неограничено и определяется в зависимости от требуемого уровня функциональности движка. Ведем третье определение, неотрывно связанное с понятием реакции: - Переменные (Vars) - это набор значений, являющихся как параметрами для реакций, так и отдельными наборами данных. К примеру: setFillColor (0,255,255) - в данном случае три значения переменных 0, 255 и 255 являются параметрами к реакции SetFillColor, устанавливающей цвет заливки, cmpVARNUMBER (R,V,N,c,J) - переменные являются параметрами функции сравнения пременной с числовым значением, var (4,100,200, 5, 17) - пременная 2 определяет, что реакция var определяет собой массив из четырех элементов - 100, 200, 5 и 17. Подытожив, можно сказать, что переменные это набор параметров рекций. Причем, некоторые реакции могут вовсе не иметь переменных (к примеру реакция, останавливающая игру при нажатии на любую клавишу), другие же могут содержать переменные по количеству требующихся реакции параметров, иные реакции, определяющие скопление данных, могут содержать большие массивы переменных. Также запомним одну важную особенность переменных - их можно изменять. Скрипты - это набор некоторых действий, выполняемых ВМ в зависимости от некоторых факторов. В качестве простого примера: при приближении игрока открываются ворота базы. Или более сложный пример: при неотключеной сигнализации при попытке открыть дверь срабатывает сирена. Во всех современных играх используется механизм скриптования игровых сцен. Вспомнить хотя бы Half-Life с его падающим шкафом при приближении. Применение скриптов достаточно широко - от изменений свойств игровых объектов и внутренних переменных до написания псевдопрограммного кода. То есть, с помощью скриптов можно не только изменять положение, наклон, скорость и прочие характеристики игровых объектов, но и изменять внутренние пременные игрового движка (к примеру, изменение плотности тумана в зависимости от времени суток с изменением заднего плана отсечения объектов или плавное изменение освещения всей сцены). Также хочется обратить особое внимание на создание скриптовых программ, являющихся по сути набором простых действий, которые выполняет движок. Становится понятно, что можно строить довольно сложные конструкции, что позволит обеспечить лучшую играбельность. Скрипт-программы обладают достаточными возможностями для изменения, сравнения внутренних переменных, а также реализацию условных и безусловных переходов, циклов, пр. Скрипт-программы как бы являются гибкой надстройкой над движком. Предоставляя скрипт-программам определенные "полномочия" по управлению движком можно добиться потрясающих эффектов. Становится легче организовывать модульность игрового движка - т.е. достаточно переопределить скрипты для объектов и получается полностью! другой сценарий, а возможно даже другой игровой мир - все зависит от сложности script-engine. Таким образом, имея один движок и сложную систему скриптов, можно создавать различные модификации игры, просто переписывая скрипт-программы. Естественно, что когда у игры появляются модификации, это продливает срок ее жизни, показывает, что игра не исчезла бесследно. Объект Event. В язык JavaScript 1.2 добавлен новый объект Event. Он содержит свойства, описывающие некое событие. Каждый раз, когда происходит какое-либо событие, объект Event передается соответствующей программе обработки. В примере на экран выводится некое изображение. Вы можете щелкнуть где-нибудь над ним клавишей мыши. В результате появится окошко сообщений, где будут показаны координаты той точки, где в этот момент находилась мышь. Код скрипта: