Уточните пожалуйста, какие документы для этого необходимо предоставить с моей стороны. Курс "Объектно-ориентированное программирование и программная инженения". |
События
Ясное понимание различий
Возможно, вы полагаете, что различие между типом события и самим событием очевидно, но в литературе эти понятия зачастую путаются, из-за чего простые вещи кажутся сложными. Это предупреждение должно помочь вам при изучении различных механизмов программирования, управляемого событиями.
Следующий текст представляет часть документации .NET от Microsoft, представляющей обработку событий, чьи концепции отражены в языках C# и Visual Basic .NET
Обзор событий
- Издатель определяет, когда событие включается; подписчики определяют, какие действия будут выполняться в ответ на событие.
- Событие может иметь много подписчиков. Подписчик может обрабатывать события, приходящие от многих издателей.
- События, не имеющие подписчиков, никогда не вызываются.
- События широко используются для сигнализации о действиях пользователя, таких как щелчки по кнопкам или выбор в меню в графическом интерфейсе пользователя.
- При наличии множества подписчиков вызов обработчиков события синхронизируется. Для асинхронного вызова смотри следующий раздел.
- События могут использоваться для синхронизации потоков.
- События в библиотеке классов .NET Framework основаны на делегате EventHandler и базовом классе EventArgs .
Здесь один и тот же термин "событие" в разных контекстах имеет разный смысл: иногда речь идет действительно о событиях, иногда о типах событий, иногда об обоих понятиях одновременно. Думаю, что вы согласитесь с моей интерпретацией. В частности:
- невозможно подписываться на события (пункты 1, 5). Как мы видели, событие не существует, пока оно не будет возбуждено. Подписчик подписывается на тип события, уведомляя, что он хочет получать уведомление о каждом событии этого типа, когда оно произойдет во время выполнения;
- в пункте 7 говорится о свойствах классов, описывающих типы событий. Заметьте, что в .NET каждый тип события объявляется как класс. Такой класс должен наследовать от класса EventHandler, представляющего классы, которые называются делегатами (delegate) — последние обеспечивают механизм, подобный агентам. Еще один класс — EventArgs — является родительским классом для классов, задающих аргументы события;
- пункт 3 звучит загадочно, пока не осознаешь, что имеется в виду следующее: "Если тип события не имеет подписчиков, включение события этого типа не дает никакого эффекта". Все это описывает внутреннюю оптимизацию: обнаружив, что тип события не имеет подписчиков, механизм события удаляет избыточно возбужденные события, что в .NET влечет к созданию объекта для каждого события.
Возможность непонимания особенно ярко проявляется в двух местах.
- В пункте 2 говорится: "Подписчик может обрабатывать множественные события от множественных издателей". Из этого комментария можно сделать вывод, что речь идет о сложной параллельной схеме вычислений, где подписчик захватывает события из различных мест одновременно, но реально это просто означает, что подписчик может регистрировать несколько типов событий и каждый издатель может включать события разных типов.
- В пункте 5 устанавливается, что для события, имеющего множество подписчиков, вызов обработчиков события синхронизируется. Предложение просто пытается сказать, что когда множество подписчиков зарегистрировали один и тот же тип события, они обрабатывают соответствующие события синхронно.
Так что при изучении схем управления событий проверяйте, о чем идет речь — о событиях или о типах событий, и убедитесь (это одно из наших очередных наставлений), что в вашей документации по событиям используется правильная терминология.
Контексты
Подписчик при регистрации говорит: "Для события этого типа выполняй это действие". На практике может быть полезно, особенно для приложений GUI, уточнить высказывание: "Для события этого типа, встречающегося в данном контексте, выполняй это действие". Например:
- "если пользователь нажал левую кнопку мыши на кнопке OK, сохрани файл";
- "если мышь находится в этом окне, измени цвет границ на красный";
- "если датчик показывает температуру выше 25° С, включи сигнал тревоги".
В первом случае "контекстом" является элемент управления, а событием — "щелчок мыши". Во втором контекст — окно, событие — появление мыши; в третьем контекст — датчик, событие — превышение режима.
Для GUI-программирования контекстом обычно является элемент управления. Как показывает последний пример, понятие контекста более общее; контекст может быть любым условием с булевским значением. Примеры GUI являются специальным случаем, где булевское условие задает свойство, такое как "курсор установлен на этой кнопке" или "курсор находится в этом окне". Вот общее определение:
Определение: контекст
Понятие контекста нам знакомо по обычному стилю программирования, не связанному с событиями: вспомните итератор, такой как do_if, который выполняет действия над всеми элементами структуры, удовлетворяющими некоторому условию.
Это аналогично тому, как контекст позволяет подписчику установить, что его интересуют события данного типа, но необходимо, чтобы в момент включения выполнялось определенное условие.
Без понятия "контекст" можно обойтись, если включать ассоциированное условие в само регистрируемое действие, например:
if "Курсор на значке Exit" then "Выполнить предусмотренные действия" end
Удобнее отделять условие, задавая его вместе с типом события и действием.
Требования "Публиковать-подписаться"
Установив концепции, будем теперь заниматься поиском общего решения проблемы, проектируя архитектуру управления событиями. Начнем с ограничений, которым должно удовлетворять хорошее решение.
Издатели и подписчики
При проектировании архитектуры, поддерживающей парадигму "Публиковать-подписаться", следует рассмотреть следующие ограничения.
- Издатели не должны знать, кто является подписчиком. Они включают события, но в соответствии с определением события не знают, кто может их обрабатывать. Типичным случаем издателя является библиотека GUI. Методы библиотеки знают, как обнаружить событие, инициированное пользователем, такое как "щелчок кнопки", но они ничего не должны знать о конкретных приложениях, реагирующих на эти события, и о том, как они реагируют. В приложении нажатие кнопки может сигнализировать о начале компиляции, запуске процесса оплаты, выключении завода или запуске ракеты. Но все это для библиотеки GUI — просто щелчок кнопки.
- Любое событие, включаемое одним издателем, может поставляться нескольким подписчикам. Изменение температуры в системе управления завода может отражаться в местах, "наблюдающих" за этим типом события. Данные могут отражаться на обычном и на графическом дисплее, в службе безопасности, включающей определенные действия при нарушениях режима, в записях изменений в базе данных.
- Подписчикам нет необходимости знать издателей. Это более строгое, но часто желательное требование. Подписчики знают о типах событий, на которые они подписываются, но не должны знать, откуда приходят события. Помните, что одна из целей проектирования, управляемого событиями, состоит в обеспечении гибкой архитектуры, позволяющей включать разных издателей и разных подписчиков, возможно, написанных разными людьми и в разные времена.
- Желательно иметь возможность во время выполнения как проводить регистрацию, так и отменять ее. В обычной схеме регистрация выполняется в фазе инициализации приложения, где устанавливаются его параметры до начала "настоящего" выполнения. Но это не является обязательным требованием — гибкость может быть полезной.
- Должно быть возможным задавать события, зависящие или не зависящие от контекста. Мы видели полезность связывания события с контекстом, но решение должно обеспечивать возможность не придумывать искусственный контекст и просто подписаться на событие независимо от того, где оно случилось.
- Связывание издателей и подписчиков должно выполняться с минимальными усилиями. Схему с событиями часто требуется добавить в уже существующее приложение. Для связывания сторон требуется добавить некоторый код, часто называемый "склеивающим кодом": чем меньше клея — тем лучше.
Последнее требование является критическим для качества системной архитектуры, особенно когда целью является построение пользовательских интерфейсов: не должно быть так, чтобы проектирование ядра зависело от особенностей интерфейса. Это наблюдение непосредственно приводит к нашим следующим понятиям — модели и облику.
Модель и облик
При проектировании интерфейса мы не только не должны различать подписчиков и издателей, но и различать два дополняющих аспекта приложения.
Определения: модель и облик программной системы
Модель (называемая также бизнес-моделью ) является той частью программной системы, которая обрабатывает данные, представляющие информацию прикладной области.
Облик - это представление части этой информации при взаимодействии системы с внешним окружением: человеком - пользователем системы, материальными устройствами, другим ПО.
В этом определении термин "прикладная область" используется в общепринятом смысле, как техническая область, в интересах которой создается и работает приложение. Для платежной системы предприятия прикладной областью является штат компании, для ПО, управляющего полетом, таковой является система управления воздушным сообщением.
Модель является частью ПО — частью, имеющей дело с прикладной областью. Для платежной системы это та часть, которая обрабатывает информацию о служащих, их часах работы, начисляет зарплату, обновляет базу данных. Для системы управления полетом — прокладывает маршрут, вычисляет времена, авторизацию и прочее. Можно сказать, что модель — это часть, выполняющая "настоящую" работу, независимо от взаимодействия с пользователями ПО и остальным миром.
Понятие "бизнес-модель" является более точным, но мы обычно предпочитаем говорить просто "модель". Одна из причин в том, что термин "бизнес" порождает неверные ассоциации (управление компанией, финансами), исключая такие области, как обработка текстов или управление полетами.
Облик задает представление информации, обычно на входе и выходе. Обликом является GUI: например, система управления полетом имеет интерфейс, позволяющий контролировать следование запланированной траектории, вводить нужные команды.
Обычно программа предназначена для одной — возможно, весьма широкой — прикладной области, но обликов у программы может быть несколько. Хорошей практикой является рассмотрение программы с разных точек зрения. При наивном проектировании небольших программ не уделяется должного внимания этой проблеме. Но для серьезных систем необходимо планировать несколько обликов, таких как:
- GUI-облик (обычно несколько);
- Web-облик ("WUI"), позволяющий использовать систему через Web-браузер;
- текстовый интерфейс — для ситуаций, когда графическая поддержка не нужна или невозможна;
- интерфейс, основанный на batch-файлах, где подготовлен сценарий, заготовлены исходные данные и вывод пишется в заданное место. Это особенно полезно для интерактивных систем. Интерактивное тестирование трудно, оно требует присутствия людей, проводящих долгие сессии, пытаясь проверить различные комбинации. Вместо этого можно подготовить коллекцию сценариев (обычно записываемых во время сессии с участием человека), а затем выполнять их без участия человека;
- облики, обеспечиваемые другими программами, которые выполняются локально; их функциональность доступна через API;
- облики Web-сервисов, обеспечиваемые программами, которые выполняются на других компьютерах; их функциональность доступна через Web-направленный API (эти сервисы требуют специальной техники, такой как протокол SOAP).
Вначале обычно достаточно одного облика. Вот почему типичной ошибкой проектирования является построение системы, где модель и облик сложно связаны. Затем, когда понадобятся другие облики, приходится прикладывать массу усилий по перепроектированию системы. Во избежание этого общим принципом должно быть разделение модели и ее обликов уже на начальных этапах проектирования системы.
Почувствуй методологию
Принцип разделения модель/облик
При проектировании архитектуры программной системы сводите к минимуму взаимодействие элементов модели и элементов облика.
Если мы используем архитектуру, управляемую событиями, то это правило хорошо сочетается с четким разделением издателей и подписчиков. Как издатели, так и подписчики взаимодействуют с обликом, но не связанными между собой способами.
- Издатели включают события, которые могут непосредственно изменить облик, обычно в малой мере. Например, курсор может изменить форму при перемещении в другое окно, нажатая кнопка может выглядеть иначе, чем кнопка в обычном состоянии.
- Подписчики захватывают события (тех типов, на которые они подписались) и обрабатывают их. Обработка может обновить облик.
Заметьте, два разделения — издатели-подписчики и модели-облики — взаимно ортогональны. Как издатели, так и подписчики могут взаимодействовать как с моделью, так и с обликами, как это можно видеть на примере системы обработки текстов.
- для издателей включать событие может возникать из-за изменения облика — пользователь передвинул мышь или нажал на кнопку — или из-за модели, когда, например, система проверки орфографии обнаружила неверно написанное слово и подчеркнула
- его.
- Обработка события подписчиком часто становится причиной модификаций как в модели, так и в облике. Например, если пользователь выделил некоторый текст и затем нажал клавишу Delete, то эффект двойной — удаляется часть хранимого текста (модель) и обновляется та часть экрана, где текст отображается (облик).
Модель - облик - контроллер
Для проектирования GUI особый интерес представляет схема "Модель — облик — контроллер" (МОК). Роль третьего элемента — контроллера — состоит в управлении интерактивной сессией. Она может включать создание и координацию обликов.
Каждая из трех частей взаимодействует с двумя другими:
Присутствие контроллера обеспечивает дальнейшее разделение между моделью и обликами (помните, что обликов может быть несколько). Контроллер управляет действиями пользователя, которые могут приводить к обновлению модели, облика, или того и другого.
Как и ранее, облик обеспечивает визуальное представление модели или части ее.
Проектировщик системы может предполагать, что пользователи понимают модель. Используя текстовый процессор, пользователь обычно знаком со шрифтами, абзацами, разделами. Пользователь, играющий в видеоигру, должен чувствовать космическое пространство и летящие ракеты. Хорошая система позволяет пользователю думать в терминах модели. Хотя то, что я вижу на экране, не более чем несколько пикселей, образующих круг, я думаю об этом как о летящем космическом корабле. Контроллер позволяет мне действовать над такими обликами, например, вращая колесико мыши, увеличивать скорость космического корабля, при этом будет обновляться как модель (изменяются ее атрибуты — скорость, позиция), так и облик, отражающий изменения в визуальном представлении.
Парадигма МОК оказала существенное влияние на скорость распространения графических интерактивных приложений за последние десятилетия. В конце лекции мы увидим, что принимая понятие проектирования, управляемого событиями с вытекающими последствиями, можно получить преимущества МОК, но с более простой архитектурой, избегая некоторых отношений, показанных на предыдущем рисунке.
Пользуясь случаем, дадим несколько полезных советов, связанных с рисунками. В презентациях ПО часто приводятся выразительные диаграммы с многочисленными блоками, связанными стрелками. Одна беда - недостаточно спецификаций, поясняющих семантику. На нашем рисунке используются для этой цели метки, такие как "представляет", "обновляет" и другие. Неименованные стрелки имеют стандартную семантику, задавая отношения наследования или "клиент-поставщик". Рисунок не хуже многих слов, если только это не просто цветовые эффекты. Не поддавайтесь соблазнам бессмысленной графики - явно задавайте точную семантику используемых символов.