Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [ 36 ] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

быть доступным, в противном случае запуск невозможен. Объект класса Concrete-Command может хранить часть нужной информации о своем окружении в виде своего состояния, а доступ к остальной информации получать во время вызова функции Execute. Чем больше информации об окружении хранится в объекте класса ConcreteCommand, тем более он независим.

С точки зрения реализации можно идентифицировать две разновидности классов ConcreteCommand. Некоторые из них просто поручают работу получателю, вызьгеая функцию-член объекта класса Receiver. Такие классы называются командами пересылки (forwarding commands). Другие классы вьтолняют более сложную работу. Они также могут вызывать функции-члены других объектов, но имеют более сложную логику функционирования. Такие классы называются активными командами (active commands).

Такая классификация команд позволяет очертить границы обобщенной реализации. Активные команды нельзя описать раз и навсегда - их код по определению зависит от конкретного приложения. В то же время на основе команд пересылки можно создавать шаблонные классы. Поскольку команды пересылки напоминают указатели на функции и похожи на функторы, мы будем называть их обобщенными функторами (generalized functors).

Оставшаяся часть главы посвящена разработке шаблонного класса Functor, инкапсулирующего любой объект, любую функцию-член этого объекта и любой набор аргументов, относящихся к этой функции. В момент активизации обобщенный функтор собирает все компоненты в одно целое, создавая вызов функции.

Объект класса Functor может оказать неоценимую помощь при проектировании с использованием шаблона Command. В реализациях, разработанных вручную, шаблон Command масштабируется не слишком хорошо - приходится писать много маленьких классов Command (по одному на каждую операцию: CmdAdduser, CmdDeleteUser, CmdModifyUser и т.д.), каждый из которых содержит тривиальную функцию-член Execute, просто вызывающую конкретную функцию-член некоторого объекта. Класс Functor, обладающий возможностью вызывать любую функцию-член любого объекта, мог бы оказать в этом деле неоценимую помощь.

В классе Functor стоит реализовать и некоторые из активных команд, например, упорядочение нескольких последовательных операций. Тогда объект класса Fuctor мог бы собирать несколько операций и выполнять их по порядку. В книге Gamma et al. (1995) такие полезные объекты описаны под именем MacroCommand.

5.2. Шаблон Command в реальном мире

Применение шаблона Command часто иллюстрируют на примере разработки оконного интерфейса. В хороших объектно-ориентированных специализированных системах для разработки графического пользовательского интерфейса шаблон command в той или иной форме применяется уже многие годы.

Создателям оконных интерфейсов необходим стандартный способ передачи пользовательских действий (например, щелчков мыши или нажатий клавиш) приложению. Когда пользователь щелкает на кнопке, выбирает пункт меню или делает что-нибудь подобное, оконная система должна уведомить об этом соответствующее приложение. С точки зрения оконной системы команда Options в меню Tools не имеет никакого особого смысла. Это накладывает на приложение очень жесткие офаничения. Объект класса Command позволяет эффективно изолировать приложение от оконного интерфейса, выступая в роли посредника, сообщающего приложению о действиях пользователя.



Например, в оконной системе в роли вызывающих объектов выступают элементы интерфейса (кнопки, пункты меню и т.п.), а получателем является объект, определяющий реакцию приложения на действия пользователя (например, диалоговое окно или само приложение).

Объекты класса Command представляют собой средство общения между пользовательским интерфейсом и приложением. Как указывалось в предьщущем разделе, класс Command обеспечивает удвоенную гибкость. Во-первых, в оконную систему можно встраивать новые элементы пользовательского интерфейса, не изменяя логику работы приложения. В таких случаях говорят, что профамма имеет оболочку (skinnable), поскольку новые элементы интерфейса можно добавлять, не касаясь самого приложения. Оболочки не имеют никакой архитектуры - они только предоставляют места для объектов класса Command и знают, как с ними взаимодействовать. Во-вторых, одни и те же элементы пользовательского интерфейса можно повторно использовать в разных приложениях.

5.3. Вызываемые сущности в языке С++

Для того чтобы создать обобщенную реализацию команд пересылки, попытаемся описать связанные с ними понятия в терминах языка С++.

Команда пересылки представляет собой обобщенный обратный вызов (generalized callback). Обратный вызов - это указатель на функцию, который можно передавать и вызывать в любое время, как показано в следующем примере.

void Foo(); void вагО;

int mainО {

определяем указатель на функцию,не имеющую параметров и возвращающую значение типа void. инициализируем этот указатель адресом функции Роо void (*pF)() = &f00;

FooO; непосредственный вызов функции Foo;

вагО; непосредственный вызов функции ваг;

(*pF)0; вызов функции Foo через указатель pF

void (*pF2)() = pF; Создаем копию указателя pF pF = &ваг; Устанавливаем указатель pF

на функцию ваг (*pF)(); вызов функции ваг через указатель pF

(*pF2)(); вызов функции Foo через указатель pF2

Между непосредственным вызовом функции Foo и вызовом через указатель (*pF) есть одно существенное отличие. Во втором случае указатель можно копировать, где-то хранить и устанавливать на другую функцию, вызывая ее при необходимости. Следовательно, указатель на функцию очень похож на команду пересылки, в которой значительная часть работы хранится отдельно от объекта, выполняющего фактическую обработку данных.

Компилятор предлагает синтаксическое сокращение: выражение (*pF)() эквивалентно pF(). Однако выражение (*pF)() более наглядно отражает суть происходящего - указатель pF разыменовывается и к нему применяется оператор вызова функции ().



Действительно, обратный вызов - это способ использования шаблона Command в языке С. Например, система X Windows хранит такой обратный вызов для каждого пункта меню и каждого элемента интерфейса (widget). Когда пользователь выполняет определенные действия (например, щелкает на кнопке), соответствующий компонент интерфейса активизирует обратный вызов, причем компонент не знает, что при этом происходит.

Кроме простых обратных вызовов, в языке С++ есть еще много сущностей, поддерживающих оператор ().

• Функции.

• Указатели на функции.

• Ссылки на функции (по существу, представляющие собой константные указатели на функции).

• Функторы, т.е. объекты, в которых определен оператор ().

• Результат применения операторов .* и ->* к указателям на функции-члены.

К каждой из указанных сущностей можно приписать пару круглых скобок, задать внутри них набор аргументов и выполнить какую-нибудь обработку данных. С другими сущностями в языке С++, кроме перечисленных выше, этого делать нельзя.

Сущности, позволяющие применять оператор (), называются вызываемыми (callable). Цель этой главы - реализовать набор команд пересылки, способных хранить и передавать вызов любой вызываемой сущности. Шаблонный класс Functor инкапсулирует команды пересылки и обеспечивает единообразный интерфейс.

Реализация должна учитывать три основных варианта: простые вызовы функций, вызовы функторов (включая вызовы объектов класса Functor, т.е. возможность передавать вызовы от одного объекта класса Functor другому) и вызовы функций-членов. Для этого следует создать абстрактный базовый класс и подкласс для каждого класса. На первый взгляд все это можно сделать с помошью средств языка С++. Однако, как только вы приступите к разработке программы, на вас обрушится ворох проблем.

5.4. Скелет шаблонного класса Functor

Для класса Functor обязательно следует проверить идиому "дескриптор-тело" ("handle-body"), впервые предложенную в работе (Coplien, 1992). В главе 7 подробно объясняется, что в языке С++ голый указатель на полиморфный тип не имеет полноценной семантики из-за проблем, связанных с его принадлежностью. Для того чтобы снять ответственность за управление временем жизни объектов с клиентов класса Functor, лучше всего наделить класс Functor семантикой значений (хорошо определенными операциями копирования и присваивания). Класс Functor имеет полиморфную реализацию, но этот факт скрыт внутри него. Назовем реализацию базового класса именем Functorlmpl.

Отметим важную особенность: функция Command: :Execute шаблона Command в языке С++ перевоплощается в оператор (), определенный пользователем. Это еще один аргумент в пользу применения оператора (). Для программистов на языке С++

Обратите внимание на то, что мы ничего не говорим о типах. Мы могли бы просто сказать: "Типы, допускающие выполнение оператора (), называются вызываемыми сущностями". Однако, хотя это кажется невероятным, в языке С++ есть сушности, не имеющие типа и в то же вре.мя позволяющие применять оператор ().



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [ 36 ] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105