Кто в ответе?
В используемом до сих пор стиле программирования порядок выполнения операций определяла программа. Он следовал из ее собственного сценария, определяемого управляющими структурами: последовательностью, условным оператором и оператором цикла. Внешний мир мог сказать свое слово — через интерфейс пользователя, доступ к базе данных, другой ввод, воздействующий на условия, циклы, динамическое связывание. Но последнее слово оставалось за программой, которая решала, где вычислять эти условия.
В этой лекции займемся исследованием другой схемы, где программа больше не задает непосредственно последовательность операций, а предоставляет множество сервисов (служб), готовых к включению в ответ на события, которые могут возникать в результате событий: нажатия пользователем кнопки, обнаружения сенсорным датчиком изменения температуры, сообщения, пришедшего на коммуникационный порт. В любой момент времени следующее событие определяет, какая служба будет востребована. Сразу же после того, как сервис выполнит свою функцию, программа возвращается в состояние ожидания событий.
Такая схема, управляемая событиями, требует подходящей инициализации: перед тем, как начнутся настоящие действия, должна быть фаза установки, регистрирующая службы в соответствии с типами возникших событий.
Такой
Эти роли не являются исключительными, поскольку некоторые подписчики могут включать собственные события. Заметьте, "событие" является программной концепцией: даже когда события возбуждаются вне ПО — нажатие кнопки мыши, измерение датчика, прибытие сообщения, — для того чтобы быть обработанными, они должны транслироваться в программные события. ПО может включать свои собственные события, не связанные с внешними воздействиями.
Программирование, управляемое событиями, применимо во многих различных областях. Оно с успехом применяется в графическом интерфейсе пользователя — GUI, что и станет нашим первым примером.
Старый добрый ввод:
(рис 7.1)
До появления GUI программы вводили данные, приходящие от некоторого последовательного посредника, например, программа могла читать последовательность строк, обрабатывая каждую из них соответствующим образом:
from
read_line
count : = 0
until
exhausted
loop
count := count + 1
— Сохранить last_line в позиции count в Result:
Result [count] := last_line
read_line
end
Здесь read_line пытается читать следующую строку ввода, сохраняя ее в last_line, а значение принимает значение true, если при выполнении read_line нет больше строк для ввода.
При такой схеме управление остается за программой : она решает, когда ей нужен очередной ввод данных. Остальной мир — здесь файл или пользователь, печатающий строки на терминале, — должен обеспечить затребованный программой ввод.
Добро пожаловать в современный мир. Если программа использует GUI, то пользователю на каждом шаге предоставляется выбор, что же он хочет делать, включая выбор, не связанный с программой, поскольку он может переключиться на работу в другом окне с другой программой, например, послать письмо по электронной почте.
Рассмотрим приведенный далее снимок экрана. Он иллюстрирует стек переполнения, возникшего из-за бесконечной рекурсии при выполнении программы в EiffelStudio. Сам пример не имеет значения — все современные среды разработки, использующие GUI, похожи. Интерфейс пользователя включает элементы управления : текстовые поля, кнопки, меню, таблицы и другие.
Ожидается, что пользователь выполняет некоторые действия по вводу данных, а наша программа соответствующим образом реагирует на них. Действиями могут быть печать текста в текстовом окне, щелчок по кнопке, выбор пункта меню.
Но какое действие будет первым, и случится ли вообще хоть что-нибудь?
(рис 7.2) Интерфейс программы
Мы не знаем.
Конечно, мы могли бы использовать большой оператор if... then ... elseif... end или конструкцию выбора со многими ветвями, перечисляя все возможности:
inspect user_action when "Нажата кнопка Stop" then "Завершить выполнение" when "Введен текст в поле Class Name" then "В соответствующее окно (слева вверху) вывести текст класса, имя которого задано в текстовом поле" when …Другие ветви… end
Но это не спасает от всех проблем, с которыми уже приходилось встречаться при обсуждении динамического связывания. Во-первых, программный код в таком случае громоздок, неуклюж и сложен. Во-вторых, что еще хуже, он чувствителен к изменениям. Нам требуется более стабильная и более простая архитектура, не требующая обновлений каждый раз при появлении нового элемента управления.
Проектирование, управляемое событиями ("издатели-подписчики"), предлагает в такой ситуации совсем другую схему.
Эту схему можно отобразить как один из экспериментов, проводимых в ядерной физике (смотри следующий рисунок), в котором поток частиц достигает экрана с небольшим отверстием и наблюдается картина, возникающая по другую сторону экрана.
(рис 7.3) Включение и обработка событий
Стиль "Издатели-подписчики" полезен в различных областях приложения: GUI-программирование — это просто один из примеров. Другие примеры:
Для рассматриваемого стиля программирования важно тщательно определить концепции, обращая, как обычно, внимание на различение типов и их экземпляров. События, издатели и подписчики
Это определение высвечивает отличительные свойства событий.
x.f (a, b, c), который удовлетворяет всем другим свойствам определения — поставляет информацию (a, b, c), доступную элементу ПО (методу f ). Но когда вызывается метод, то явно указывается адресат вызова. При вызове событий все не так — информация посылается в межадресное пространство, где любой элемент может использовать ее в интересах своей работы.Помните, что для наших целей событие — это операция ПО. Могут существовать внешние события — действия пользователя и прочее, — приводящие к возникновению программных событий, но и сама программа может создавать события в процессе работы.
Рассмотрим связанную с этими понятиями терминологию, неформально уже встречавшуюся.
Один и тот же элемент ПО может быть издателем и подписчиком, в частности, благодаря общей для подписчиков схеме — включать собственное событие как реакцию на возникновение пришедшего к нему события.
В литературе встречаются синонимы приведенных выше терминов: подписчики называются "наблюдателями", отсюда образец "Наблюдатель", изучаемый в этой лекции. Говорят также о "слушателях" события. Для "издателей" также используется синоним, вытекающий из подобной риторики — "предмет наблюдения" .
Нам необходимо имя для информации, приходящей — в соответствии с определением — с любым событием.
Термин "аргумент" указывает на сходство с аргументами метода. Продолжая это сходство, будем предполагать, что аргументы сгруппированы в упорядоченный список, подобно аргументам в вызове x.f (a, b, c) . Как и для методов, список может быть пустым, например, в случае события, указывающего на истечение срока ожидания.
Как подписчики обнаруживают, что произошло интересующее их событие? Одна модель — опрос (повторяющаяся проверка). Это напоминает ситуацию с подписчиком журнала, который в день издания ходит к почтовому ящику, чтобы проверить, не пришел ли журнал. Вторая модель — уведомление: при включении события об этом уведомляются все подписчики.
Модели для информации, распределенной в сети Интернет, классифицируются как "притяжение" (ожидание пользователей, заинтересованных в информации) и "проталкивание" (рассылка информации пользователям).
Модель уведомления — более гибкая, и далее будем предполагать именно ее. Она может работать при условии, что подписчики заранее явно проявили свой интерес. Дело обстоит так же как с подписчиками журнала: на журнал надо заранее подписаться. Но на что можно подписаться? На событие подписаться нельзя, поскольку, по определению, это операция, выполняемая в ходе работы программы: до его появления оно не существует, а после — уже поздно.
То, что нужно знать подписчикам, это тип события, описывающий возможные события, разделяющие общие характеристики. Например, все щелчки левой кнопки мыши — это события одного типа, но отличающиеся от конкретного события нажатия клавиши. Понятие типа события играет центральную роль в проектировании, управляемом событиями, и будет представлять центральную абстракцию в нашем поиске хорошей ОО-архитектуры.
Все события одного типа имеют один и тот же список аргументов. Например, список аргументов для события — щелчок левой кнопки мыши — включает координаты мыши в момент щелчка — два целых числа. Во многом концепция заимствована у методов, обладающих сигнатурой — списком типов аргументов. Процедура print (v: VALUE; f: FORMAT) имеет сигнатуру [VALUE, FORMAT] — список типов. Это понятие расширяется на типы события.
Все события данного типа события имеют одну и ту же сигнатуру. Например:
[REAL, REAL], чтобы представлять старое и новое значения;[INTEGER, INTEGER] .Можно также взять событие "одиночный щелчок мыши" с третьим компонентом сигнатуры, указывающим, какая кнопка была нажата. В библиотеке EiffelVision применяется вариант, в котором добавлен еще один аргумент, показывающий, остается ли нажатой кнопка, - он полезен (особенно в играх) для джойстика и экзотичных устройств указателей;
[CHARACTER], где аргумент задает код клавиши;Всякий раз, когда издатель включает событие, он должен обеспечить значение каждого аргумента (если они есть): координаты мыши, код клавиши, температуры. И здесь наблюдается полная аналогия с вызовом метода, где при каждом вызове задаются фактические аргументы.
Термин "тип события" может предполагать еще одну аналогию, где каждый тип соответствует типам ОО-программирования (классам с возможно родовыми параметрами), а каждое событие соответствует экземпляру класса (объектам). Но сравнение типов событий с программами - более показательное, тогда появление события данного типа соответствует вызову программы.
При таком подходе событие — это не объект, а тип события — это не класс. Вместо этого можно ввести общее понятие для всех типов событий: класс, называемый ниже EVENT_TYPE, а конкретный тип события, например, "щелчок левой кнопки мыши" (абстракция левых щелчков — вроде того, что я в прошлый понедельник, будучи в расстроенных чувствах, щелкнул в ответ на запрос "Delete all?" ) рассматривать как объект. Как всегда, когда вы раздумываете, не ввести ли класс, критерием является "Можно ли эту абстракцию данных наполнить смыслом, определив множество хорошо понимаемых операций, применимых ко всем объектам класса?". В данном случае:
Не рассматривать каждое событие как объект полезно и с позиций производительности. Обычно во время выполнения создается большое число событий - каждое малое перемещение курсора включает событие, так что следует избегать создания всех соответствующих объектов. Это не освобождает нас от нагрузки, поскольку аргументы каждого события, представленные кортежем, должны быть записаны. В хорошей библиотеке GUI производительность улучшается за счет того, что для последовательности близких событий можно использовать один кортеж вместо десятков сотен.
Полезно иметь термины для действий подписчиков с типами событий и событиями.
Элемент ПО может стать подписчиком некоторого типа событий, подписавшись (зарегистрировавшись) на него. После регистрации элемент будет получать уведомления о всех возникающих событиях этого типа, так что он может получить аргументы и выполнить в ответ специфические действия.
Когда элемент получает уведомление о событии, на которое он подписан, он обрабатывает (или захватывает ) событие, выполняя зарегистрированное действие.
Хотя регистрацию (дерегистрацию) можно выполнить в любой момент, общепринято иметь фазу инициализации, расставляя подписчиков по местам, после чего уже выполняется главный этап выполнения, на котором издатели включают события, а подписчики их обрабатывают.
При регистрации подписчик задает некоторое действие, выполняемое в ответ на возникновение события данного типа. У действия должен быть способ получения аргументов события. Очевидный путь достижения цели — это указание при регистрации метода, чья сигнатура соответствует сигнатуре типа события. Тогда возникновение события данного типа будет причиной вызова метода, обрабатывающего событие, где аргументы события играют роль фактических аргументов вызова.
Теперь у нас есть полная картина того, как работает программа, управляемая событиями.
E1 Некоторые элементы, издатели, позволяют остальной системе узнать, какие типы событий они могут включать.
E2 Некоторые элементы, подписчики, заинтересованы в обработке событий определенных типов. Они регистрируют соответствующие действия.
E3 В любой момент издатель может включить событие. Это приведет к выполнению действий, зарегистрированных подписчиками для события данного типа. Эти действия могут использовать аргументы события.
В примере GUI:
E1 Издателем является некоторый элемент ПО, который следит за устройствами ввода и включает события при определенных обстоятельствах, например, при нажатии клавиш клавиатуры или кнопок мыши. Обычно нет необходимости писать такое ПО, поскольку оно является частью библиотеки GUI - EiffelVision для Eiffel, Windows Forms для .NET, Swing для Java.
E2 Подписчиком является любой элемент, которому требуется обработать GUI-события. Он регистрирует методы, выполняемые в ответ на события. Например, метод, сохраняющий файл, можно зарегистрировать для события щелчок мыши на кнопке с надписью "OK" в диалоге по сохранению файла.
E3 Если во время выполнения пользователь щелкнет по кнопке OK, то это станет причиной выполнения метода — или методов, — зарегистрированных для данного типа события.
Важное свойство этой схемы, проиллюстрированное на последнем рисунке: выделение двух сторон, участвующих в создании и обработке события, — подписчики и издатели ничего не знают друг о друге. Более точно, определение "события" требует, чтобы подписчики не знали других подписчиков. Остальное — дело методологии, и мы увидим, как различные архитектурные решения достигают результата по отношению к этому критерию.
Возможно, вы полагаете, что различие между типом события и самим событием очевидно, но в литературе эти понятия зачастую путаются, из-за чего простые вещи кажутся сложными. Это предупреждение должно помочь вам при изучении различных механизмов программирования, управляемого событиями.
Следующий текст представляет часть документации .NET от Microsoft, представляющей обработку событий, чьи концепции отражены в языках C# и Visual Basic .NET
EventHandler и базовом классе EventArgs .Здесь один и тот же термин "событие" в разных контекстах имеет разный смысл: иногда речь идет действительно о событиях, иногда о типах событий, иногда об обоих понятиях одновременно. Думаю, что вы согласитесь с моей интерпретацией. В частности:
EventHandler, представляющего классы, которые называются делегатами (delegate) — последние обеспечивают механизм, подобный агентам. Еще один класс — EventArgs — является родительским классом для классов, задающих аргументы события;Возможность непонимания особенно ярко проявляется в двух местах.
Так что при изучении схем управления событий проверяйте, о чем идет речь — о событиях или о типах событий, и убедитесь (это одно из наших очередных наставлений), что в вашей документации по событиям используется правильная терминология.
Подписчик при регистрации говорит: "Для события этого типа выполняй это действие". На практике может быть полезно, особенно для приложений GUI, уточнить высказывание: "Для события этого типа, встречающегося в данном контексте, выполняй это действие". Например:
В первом случае "контекстом" является элемент управления, а событием — "щелчок мыши". Во втором контекст — окно, событие — появление мыши; в третьем контекст — датчик, событие — превышение режима.
Для GUI-программирования контекстом обычно является элемент управления. Как показывает последний пример, понятие контекста более общее; контекст может быть любым условием с булевским значением. Примеры GUI являются специальным случаем, где булевское условие задает свойство, такое как "курсор установлен на этой кнопке" или "курсор находится в этом окне". Вот общее определение:
Понятие контекста нам знакомо по обычному стилю программирования, не связанному с событиями: вспомните итератор, такой как do_if, который выполняет действия над всеми элементами структуры, удовлетворяющими некоторому условию.
Это аналогично тому, как контекст позволяет подписчику установить, что его интересуют события данного типа, но необходимо, чтобы в момент включения выполнялось определенное условие.
Без понятия "контекст" можно обойтись, если включать ассоциированное условие в само регистрируемое действие, например:
if "Курсор на значке Exit" then "Выполнить предусмотренные действия" end
Удобнее отделять условие, задавая его вместе с типом события и действием.
Установив концепции, будем теперь заниматься поиском общего решения проблемы, проектируя архитектуру управления событиями. Начнем с ограничений, которым должно удовлетворять хорошее решение.
При проектировании архитектуры, поддерживающей парадигму "Публиковать-подписаться", следует рассмотреть следующие ограничения.
Последнее требование является критическим для качества системной архитектуры, особенно когда целью является построение пользовательских интерфейсов: не должно быть так, чтобы проектирование ядра зависело от особенностей интерфейса. Это наблюдение непосредственно приводит к нашим следующим понятиям — модели и облику.
При проектировании интерфейса мы не только не должны различать подписчиков и издателей, но и различать два дополняющих аспекта приложения.
Модель (называемая также бизнес-моделью ) является той частью программной системы, которая обрабатывает данные, представляющие информацию прикладной области.
Облик - это представление части этой информации при взаимодействии системы с внешним окружением: человеком - пользователем системы, материальными устройствами, другим ПО.
В этом определении термин "прикладная область" используется в общепринятом смысле, как техническая область, в интересах которой создается и работает приложение. Для платежной системы предприятия прикладной областью является штат компании, для ПО, управляющего полетом, таковой является система управления воздушным сообщением.
Модель является частью ПО — частью, имеющей дело с прикладной областью. Для платежной системы это та часть, которая обрабатывает информацию о служащих, их часах работы, начисляет зарплату, обновляет базу данных. Для системы управления полетом — прокладывает маршрут, вычисляет времена, авторизацию и прочее. Можно сказать, что модель — это часть, выполняющая "настоящую" работу, независимо от взаимодействия с пользователями ПО и остальным миром.
Понятие "бизнес-модель" является более точным, но мы обычно предпочитаем говорить просто "модель". Одна из причин в том, что термин "бизнес" порождает неверные ассоциации (управление компанией, финансами), исключая такие области, как обработка текстов или управление полетами.
Облик задает представление информации, обычно на входе и выходе. Обликом является GUI: например, система управления полетом имеет интерфейс, позволяющий контролировать следование запланированной траектории, вводить нужные команды.
Обычно программа предназначена для одной — возможно, весьма широкой — прикладной области, но обликов у программы может быть несколько. Хорошей практикой является рассмотрение программы с разных точек зрения. При наивном проектировании небольших программ не уделяется должного внимания этой проблеме. Но для серьезных систем необходимо планировать несколько обликов, таких как:
Вначале обычно достаточно одного облика. Вот почему типичной ошибкой проектирования является построение системы, где модель и облик сложно связаны. Затем, когда понадобятся другие облики, приходится прикладывать массу усилий по перепроектированию системы. Во избежание этого общим принципом должно быть разделение модели и ее обликов уже на начальных этапах проектирования системы.
Принцип разделения модель/облик
При проектировании архитектуры программной системы сводите к минимуму взаимодействие элементов модели и элементов облика.
Если мы используем архитектуру, управляемую событиями, то это правило хорошо сочетается с четким разделением издателей и подписчиков. Как издатели, так и подписчики взаимодействуют с обликом, но не связанными между собой способами.
Заметьте, два разделения — издатели-подписчики и модели-облики — взаимно ортогональны. Как издатели, так и подписчики могут взаимодействовать как с моделью, так и с обликами, как это можно видеть на примере системы обработки текстов.
Для проектирования GUI особый интерес представляет схема "Модель — облик — контроллер" (МОК). Роль третьего элемента — контроллера — состоит в управлении интерактивной сессией. Она может включать создание и координацию обликов.
Каждая из трех частей взаимодействует с двумя другими:
(рис 7.4) Структура МОК
Присутствие контроллера обеспечивает дальнейшее разделение между моделью и обликами (помните, что обликов может быть несколько). Контроллер управляет действиями пользователя, которые могут приводить к обновлению модели, облика, или того и другого.
Как и ранее, облик обеспечивает визуальное представление модели или части ее.
Проектировщик системы может предполагать, что пользователи понимают модель. Используя текстовый процессор, пользователь обычно знаком со шрифтами, абзацами, разделами. Пользователь, играющий в видеоигру, должен чувствовать космическое пространство и летящие ракеты. Хорошая система позволяет пользователю думать в терминах модели. Хотя то, что я вижу на экране, не более чем несколько пикселей, образующих круг, я думаю об этом как о летящем космическом корабле. Контроллер позволяет мне действовать над такими обликами, например, вращая колесико мыши, увеличивать скорость космического корабля, при этом будет обновляться как модель (изменяются ее атрибуты — скорость, позиция), так и облик, отражающий изменения в визуальном представлении.
Парадигма МОК оказала существенное влияние на скорость распространения графических интерактивных приложений за последние десятилетия. В конце лекции мы увидим, что принимая понятие проектирования, управляемого событиями с вытекающими последствиями, можно получить преимущества МОК, но с более простой архитектурой, избегая некоторых отношений, показанных на предыдущем рисунке.
Пользуясь случаем, дадим несколько полезных советов, связанных с рисунками. В презентациях ПО часто приводятся выразительные диаграммы с многочисленными блоками, связанными стрелками. Одна беда - недостаточно спецификаций, поясняющих семантику. На нашем рисунке используются для этой цели метки, такие как "представляет", "обновляет" и другие. Неименованные стрелки имеют стандартную семантику, задавая отношения наследования или "клиент-поставщик". Рисунок не хуже многих слов, если только это не просто цветовые эффекты. Не поддавайтесь соблазнам бессмысленной графики - явно задавайте точную семантику используемых символов.
Кто в ответе?
В используемом до сих пор стиле программирования порядок выполнения операций определяла программа. Он следовал из ее собственного сценария, определяемого управляющими структурами: последовательностью, условным оператором и оператором цикла. Внешний мир мог сказать свое слово — через интерфейс пользователя, доступ к базе данных, другой ввод, воздействующий на условия, циклы, динамическое связывание. Но последнее слово оставалось за программой, которая решала, где вычислять эти условия.
В этой лекции займемся исследованием другой схемы, где программа больше не задает непосредственно последовательность операций, а предоставляет множество сервисов (служб), готовых к включению в ответ на события, которые могут возникать в результате событий: нажатия пользователем кнопки, обнаружения сенсорным датчиком изменения температуры, сообщения, пришедшего на коммуникационный порт. В любой момент времени следующее событие определяет, какая служба будет востребована. Сразу же после того, как сервис выполнит свою функцию, программа возвращается в состояние ожидания событий.
Такая схема, управляемая событиями, требует подходящей инициализации: перед тем, как начнутся настоящие действия, должна быть фаза установки, регистрирующая службы в соответствии с типами возникших событий.
Такой
Эти роли не являются исключительными, поскольку некоторые подписчики могут включать собственные события. Заметьте, "событие" является программной концепцией: даже когда события возбуждаются вне ПО — нажатие кнопки мыши, измерение датчика, прибытие сообщения, — для того чтобы быть обработанными, они должны транслироваться в программные события. ПО может включать свои собственные события, не связанные с внешними воздействиями.
Программирование, управляемое событиями, применимо во многих различных областях. Оно с успехом применяется в графическом интерфейсе пользователя — GUI, что и станет нашим первым примером.
Старый добрый ввод:
(рис 7.1)
До появления GUI программы вводили данные, приходящие от некоторого последовательного посредника, например, программа могла читать последовательность строк, обрабатывая каждую из них соответствующим образом:
from
read_line
count : = 0
until
exhausted
loop
count := count + 1
— Сохранить last_line в позиции count в Result:
Result [count] := last_line
read_line
end
Здесь read_line пытается читать следующую строку ввода, сохраняя ее в last_line, а значение принимает значение true, если при выполнении read_line нет больше строк для ввода.
При такой схеме управление остается за программой : она решает, когда ей нужен очередной ввод данных. Остальной мир — здесь файл или пользователь, печатающий строки на терминале, — должен обеспечить затребованный программой ввод.
Добро пожаловать в современный мир. Если программа использует GUI, то пользователю на каждом шаге предоставляется выбор, что же он хочет делать, включая выбор, не связанный с программой, поскольку он может переключиться на работу в другом окне с другой программой, например, послать письмо по электронной почте.
Рассмотрим приведенный далее снимок экрана. Он иллюстрирует стек переполнения, возникшего из-за бесконечной рекурсии при выполнении программы в EiffelStudio. Сам пример не имеет значения — все современные среды разработки, использующие GUI, похожи. Интерфейс пользователя включает элементы управления : текстовые поля, кнопки, меню, таблицы и другие.
Ожидается, что пользователь выполняет некоторые действия по вводу данных, а наша программа соответствующим образом реагирует на них. Действиями могут быть печать текста в текстовом окне, щелчок по кнопке, выбор пункта меню.
Но какое действие будет первым, и случится ли вообще хоть что-нибудь?
(рис 7.2) Интерфейс программы
Мы не знаем.
Конечно, мы могли бы использовать большой оператор if... then ... elseif... end или конструкцию выбора со многими ветвями, перечисляя все возможности:
inspect user_action when "Нажата кнопка Stop" then "Завершить выполнение" when "Введен текст в поле Class Name" then "В соответствующее окно (слева вверху) вывести текст класса, имя которого задано в текстовом поле" when …Другие ветви… end
Но это не спасает от всех проблем, с которыми уже приходилось встречаться при обсуждении динамического связывания. Во-первых, программный код в таком случае громоздок, неуклюж и сложен. Во-вторых, что еще хуже, он чувствителен к изменениям. Нам требуется более стабильная и более простая архитектура, не требующая обновлений каждый раз при появлении нового элемента управления.
Проектирование, управляемое событиями ("издатели-подписчики"), предлагает в такой ситуации совсем другую схему.
Эту схему можно отобразить как один из экспериментов, проводимых в ядерной физике (смотри следующий рисунок), в котором поток частиц достигает экрана с небольшим отверстием и наблюдается картина, возникающая по другую сторону экрана.
(рис 7.3) Включение и обработка событий
Стиль "Издатели-подписчики" полезен в различных областях приложения: GUI-программирование — это просто один из примеров. Другие примеры:
Для рассматриваемого стиля программирования важно тщательно определить концепции, обращая, как обычно, внимание на различение типов и их экземпляров. События, издатели и подписчики
Это определение высвечивает отличительные свойства событий.
x.f (a, b, c), который удовлетворяет всем другим свойствам определения — поставляет информацию (a, b, c), доступную элементу ПО (методу f ). Но когда вызывается метод, то явно указывается адресат вызова. При вызове событий все не так — информация посылается в межадресное пространство, где любой элемент может использовать ее в интересах своей работы.Помните, что для наших целей событие — это операция ПО. Могут существовать внешние события — действия пользователя и прочее, — приводящие к возникновению программных событий, но и сама программа может создавать события в процессе работы.
Рассмотрим связанную с этими понятиями терминологию, неформально уже встречавшуюся.
Один и тот же элемент ПО может быть издателем и подписчиком, в частности, благодаря общей для подписчиков схеме — включать собственное событие как реакцию на возникновение пришедшего к нему события.
В литературе встречаются синонимы приведенных выше терминов: подписчики называются "наблюдателями", отсюда образец "Наблюдатель", изучаемый в этой лекции. Говорят также о "слушателях" события. Для "издателей" также используется синоним, вытекающий из подобной риторики — "предмет наблюдения" .
Нам необходимо имя для информации, приходящей — в соответствии с определением — с любым событием.
Термин "аргумент" указывает на сходство с аргументами метода. Продолжая это сходство, будем предполагать, что аргументы сгруппированы в упорядоченный список, подобно аргументам в вызове x.f (a, b, c) . Как и для методов, список может быть пустым, например, в случае события, указывающего на истечение срока ожидания.
Как подписчики обнаруживают, что произошло интересующее их событие? Одна модель — опрос (повторяющаяся проверка). Это напоминает ситуацию с подписчиком журнала, который в день издания ходит к почтовому ящику, чтобы проверить, не пришел ли журнал. Вторая модель — уведомление: при включении события об этом уведомляются все подписчики.
Модели для информации, распределенной в сети Интернет, классифицируются как "притяжение" (ожидание пользователей, заинтересованных в информации) и "проталкивание" (рассылка информации пользователям).
Модель уведомления — более гибкая, и далее будем предполагать именно ее. Она может работать при условии, что подписчики заранее явно проявили свой интерес. Дело обстоит так же как с подписчиками журнала: на журнал надо заранее подписаться. Но на что можно подписаться? На событие подписаться нельзя, поскольку, по определению, это операция, выполняемая в ходе работы программы: до его появления оно не существует, а после — уже поздно.
То, что нужно знать подписчикам, это тип события, описывающий возможные события, разделяющие общие характеристики. Например, все щелчки левой кнопки мыши — это события одного типа, но отличающиеся от конкретного события нажатия клавиши. Понятие типа события играет центральную роль в проектировании, управляемом событиями, и будет представлять центральную абстракцию в нашем поиске хорошей ОО-архитектуры.
Все события одного типа имеют один и тот же список аргументов. Например, список аргументов для события — щелчок левой кнопки мыши — включает координаты мыши в момент щелчка — два целых числа. Во многом концепция заимствована у методов, обладающих сигнатурой — списком типов аргументов. Процедура print (v: VALUE; f: FORMAT) имеет сигнатуру [VALUE, FORMAT] — список типов. Это понятие расширяется на типы события.
Все события данного типа события имеют одну и ту же сигнатуру. Например:
[REAL, REAL], чтобы представлять старое и новое значения;[INTEGER, INTEGER] .Можно также взять событие "одиночный щелчок мыши" с третьим компонентом сигнатуры, указывающим, какая кнопка была нажата. В библиотеке EiffelVision применяется вариант, в котором добавлен еще один аргумент, показывающий, остается ли нажатой кнопка, - он полезен (особенно в играх) для джойстика и экзотичных устройств указателей;
[CHARACTER], где аргумент задает код клавиши;Всякий раз, когда издатель включает событие, он должен обеспечить значение каждого аргумента (если они есть): координаты мыши, код клавиши, температуры. И здесь наблюдается полная аналогия с вызовом метода, где при каждом вызове задаются фактические аргументы.
Термин "тип события" может предполагать еще одну аналогию, где каждый тип соответствует типам ОО-программирования (классам с возможно родовыми параметрами), а каждое событие соответствует экземпляру класса (объектам). Но сравнение типов событий с программами - более показательное, тогда появление события данного типа соответствует вызову программы.
При таком подходе событие — это не объект, а тип события — это не класс. Вместо этого можно ввести общее понятие для всех типов событий: класс, называемый ниже EVENT_TYPE, а конкретный тип события, например, "щелчок левой кнопки мыши" (абстракция левых щелчков — вроде того, что я в прошлый понедельник, будучи в расстроенных чувствах, щелкнул в ответ на запрос "Delete all?" ) рассматривать как объект. Как всегда, когда вы раздумываете, не ввести ли класс, критерием является "Можно ли эту абстракцию данных наполнить смыслом, определив множество хорошо понимаемых операций, применимых ко всем объектам класса?". В данном случае:
Не рассматривать каждое событие как объект полезно и с позиций производительности. Обычно во время выполнения создается большое число событий - каждое малое перемещение курсора включает событие, так что следует избегать создания всех соответствующих объектов. Это не освобождает нас от нагрузки, поскольку аргументы каждого события, представленные кортежем, должны быть записаны. В хорошей библиотеке GUI производительность улучшается за счет того, что для последовательности близких событий можно использовать один кортеж вместо десятков сотен.
Полезно иметь термины для действий подписчиков с типами событий и событиями.
Элемент ПО может стать подписчиком некоторого типа событий, подписавшись (зарегистрировавшись) на него. После регистрации элемент будет получать уведомления о всех возникающих событиях этого типа, так что он может получить аргументы и выполнить в ответ специфические действия.
Когда элемент получает уведомление о событии, на которое он подписан, он обрабатывает (или захватывает ) событие, выполняя зарегистрированное действие.
Хотя регистрацию (дерегистрацию) можно выполнить в любой момент, общепринято иметь фазу инициализации, расставляя подписчиков по местам, после чего уже выполняется главный этап выполнения, на котором издатели включают события, а подписчики их обрабатывают.
При регистрации подписчик задает некоторое действие, выполняемое в ответ на возникновение события данного типа. У действия должен быть способ получения аргументов события. Очевидный путь достижения цели — это указание при регистрации метода, чья сигнатура соответствует сигнатуре типа события. Тогда возникновение события данного типа будет причиной вызова метода, обрабатывающего событие, где аргументы события играют роль фактических аргументов вызова.
Теперь у нас есть полная картина того, как работает программа, управляемая событиями.
E1 Некоторые элементы, издатели, позволяют остальной системе узнать, какие типы событий они могут включать.
E2 Некоторые элементы, подписчики, заинтересованы в обработке событий определенных типов. Они регистрируют соответствующие действия.
E3 В любой момент издатель может включить событие. Это приведет к выполнению действий, зарегистрированных подписчиками для события данного типа. Эти действия могут использовать аргументы события.
В примере GUI:
E1 Издателем является некоторый элемент ПО, который следит за устройствами ввода и включает события при определенных обстоятельствах, например, при нажатии клавиш клавиатуры или кнопок мыши. Обычно нет необходимости писать такое ПО, поскольку оно является частью библиотеки GUI - EiffelVision для Eiffel, Windows Forms для .NET, Swing для Java.
E2 Подписчиком является любой элемент, которому требуется обработать GUI-события. Он регистрирует методы, выполняемые в ответ на события. Например, метод, сохраняющий файл, можно зарегистрировать для события щелчок мыши на кнопке с надписью "OK" в диалоге по сохранению файла.
E3 Если во время выполнения пользователь щелкнет по кнопке OK, то это станет причиной выполнения метода — или методов, — зарегистрированных для данного типа события.
Важное свойство этой схемы, проиллюстрированное на последнем рисунке: выделение двух сторон, участвующих в создании и обработке события, — подписчики и издатели ничего не знают друг о друге. Более точно, определение "события" требует, чтобы подписчики не знали других подписчиков. Остальное — дело методологии, и мы увидим, как различные архитектурные решения достигают результата по отношению к этому критерию.
Возможно, вы полагаете, что различие между типом события и самим событием очевидно, но в литературе эти понятия зачастую путаются, из-за чего простые вещи кажутся сложными. Это предупреждение должно помочь вам при изучении различных механизмов программирования, управляемого событиями.
Следующий текст представляет часть документации .NET от Microsoft, представляющей обработку событий, чьи концепции отражены в языках C# и Visual Basic .NET
EventHandler и базовом классе EventArgs .Здесь один и тот же термин "событие" в разных контекстах имеет разный смысл: иногда речь идет действительно о событиях, иногда о типах событий, иногда об обоих понятиях одновременно. Думаю, что вы согласитесь с моей интерпретацией. В частности:
EventHandler, представляющего классы, которые называются делегатами (delegate) — последние обеспечивают механизм, подобный агентам. Еще один класс — EventArgs — является родительским классом для классов, задающих аргументы события;Возможность непонимания особенно ярко проявляется в двух местах.
Так что при изучении схем управления событий проверяйте, о чем идет речь — о событиях или о типах событий, и убедитесь (это одно из наших очередных наставлений), что в вашей документации по событиям используется правильная терминология.
Подписчик при регистрации говорит: "Для события этого типа выполняй это действие". На практике может быть полезно, особенно для приложений GUI, уточнить высказывание: "Для события этого типа, встречающегося в данном контексте, выполняй это действие". Например:
В первом случае "контекстом" является элемент управления, а событием — "щелчок мыши". Во втором контекст — окно, событие — появление мыши; в третьем контекст — датчик, событие — превышение режима.
Для GUI-программирования контекстом обычно является элемент управления. Как показывает последний пример, понятие контекста более общее; контекст может быть любым условием с булевским значением. Примеры GUI являются специальным случаем, где булевское условие задает свойство, такое как "курсор установлен на этой кнопке" или "курсор находится в этом окне". Вот общее определение:
Понятие контекста нам знакомо по обычному стилю программирования, не связанному с событиями: вспомните итератор, такой как do_if, который выполняет действия над всеми элементами структуры, удовлетворяющими некоторому условию.
Это аналогично тому, как контекст позволяет подписчику установить, что его интересуют события данного типа, но необходимо, чтобы в момент включения выполнялось определенное условие.
Без понятия "контекст" можно обойтись, если включать ассоциированное условие в само регистрируемое действие, например:
if "Курсор на значке Exit" then "Выполнить предусмотренные действия" end
Удобнее отделять условие, задавая его вместе с типом события и действием.
Установив концепции, будем теперь заниматься поиском общего решения проблемы, проектируя архитектуру управления событиями. Начнем с ограничений, которым должно удовлетворять хорошее решение.
При проектировании архитектуры, поддерживающей парадигму "Публиковать-подписаться", следует рассмотреть следующие ограничения.
Последнее требование является критическим для качества системной архитектуры, особенно когда целью является построение пользовательских интерфейсов: не должно быть так, чтобы проектирование ядра зависело от особенностей интерфейса. Это наблюдение непосредственно приводит к нашим следующим понятиям — модели и облику.
При проектировании интерфейса мы не только не должны различать подписчиков и издателей, но и различать два дополняющих аспекта приложения.
Модель (называемая также бизнес-моделью ) является той частью программной системы, которая обрабатывает данные, представляющие информацию прикладной области.
Облик - это представление части этой информации при взаимодействии системы с внешним окружением: человеком - пользователем системы, материальными устройствами, другим ПО.
В этом определении термин "прикладная область" используется в общепринятом смысле, как техническая область, в интересах которой создается и работает приложение. Для платежной системы предприятия прикладной областью является штат компании, для ПО, управляющего полетом, таковой является система управления воздушным сообщением.
Модель является частью ПО — частью, имеющей дело с прикладной областью. Для платежной системы это та часть, которая обрабатывает информацию о служащих, их часах работы, начисляет зарплату, обновляет базу данных. Для системы управления полетом — прокладывает маршрут, вычисляет времена, авторизацию и прочее. Можно сказать, что модель — это часть, выполняющая "настоящую" работу, независимо от взаимодействия с пользователями ПО и остальным миром.
Понятие "бизнес-модель" является более точным, но мы обычно предпочитаем говорить просто "модель". Одна из причин в том, что термин "бизнес" порождает неверные ассоциации (управление компанией, финансами), исключая такие области, как обработка текстов или управление полетами.
Облик задает представление информации, обычно на входе и выходе. Обликом является GUI: например, система управления полетом имеет интерфейс, позволяющий контролировать следование запланированной траектории, вводить нужные команды.
Обычно программа предназначена для одной — возможно, весьма широкой — прикладной области, но обликов у программы может быть несколько. Хорошей практикой является рассмотрение программы с разных точек зрения. При наивном проектировании небольших программ не уделяется должного внимания этой проблеме. Но для серьезных систем необходимо планировать несколько обликов, таких как:
Вначале обычно достаточно одного облика. Вот почему типичной ошибкой проектирования является построение системы, где модель и облик сложно связаны. Затем, когда понадобятся другие облики, приходится прикладывать массу усилий по перепроектированию системы. Во избежание этого общим принципом должно быть разделение модели и ее обликов уже на начальных этапах проектирования системы.
Принцип разделения модель/облик
При проектировании архитектуры программной системы сводите к минимуму взаимодействие элементов модели и элементов облика.
Если мы используем архитектуру, управляемую событиями, то это правило хорошо сочетается с четким разделением издателей и подписчиков. Как издатели, так и подписчики взаимодействуют с обликом, но не связанными между собой способами.
Заметьте, два разделения — издатели-подписчики и модели-облики — взаимно ортогональны. Как издатели, так и подписчики могут взаимодействовать как с моделью, так и с обликами, как это можно видеть на примере системы обработки текстов.
Для проектирования GUI особый интерес представляет схема "Модель — облик — контроллер" (МОК). Роль третьего элемента — контроллера — состоит в управлении интерактивной сессией. Она может включать создание и координацию обликов.
Каждая из трех частей взаимодействует с двумя другими:
(рис 7.4) Структура МОК
Присутствие контроллера обеспечивает дальнейшее разделение между моделью и обликами (помните, что обликов может быть несколько). Контроллер управляет действиями пользователя, которые могут приводить к обновлению модели, облика, или того и другого.
Как и ранее, облик обеспечивает визуальное представление модели или части ее.
Проектировщик системы может предполагать, что пользователи понимают модель. Используя текстовый процессор, пользователь обычно знаком со шрифтами, абзацами, разделами. Пользователь, играющий в видеоигру, должен чувствовать космическое пространство и летящие ракеты. Хорошая система позволяет пользователю думать в терминах модели. Хотя то, что я вижу на экране, не более чем несколько пикселей, образующих круг, я думаю об этом как о летящем космическом корабле. Контроллер позволяет мне действовать над такими обликами, например, вращая колесико мыши, увеличивать скорость космического корабля, при этом будет обновляться как модель (изменяются ее атрибуты — скорость, позиция), так и облик, отражающий изменения в визуальном представлении.
Парадигма МОК оказала существенное влияние на скорость распространения графических интерактивных приложений за последние десятилетия. В конце лекции мы увидим, что принимая понятие проектирования, управляемого событиями с вытекающими последствиями, можно получить преимущества МОК, но с более простой архитектурой, избегая некоторых отношений, показанных на предыдущем рисунке.
Пользуясь случаем, дадим несколько полезных советов, связанных с рисунками. В презентациях ПО часто приводятся выразительные диаграммы с многочисленными блоками, связанными стрелками. Одна беда - недостаточно спецификаций, поясняющих семантику. На нашем рисунке используются для этой цели метки, такие как "представляет", "обновляет" и другие. Неименованные стрелки имеют стандартную семантику, задавая отношения наследования или "клиент-поставщик". Рисунок не хуже многих слов, если только это не просто цветовые эффекты. Не поддавайтесь соблазнам бессмысленной графики - явно задавайте точную семантику используемых символов.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.