Анимация
JavaScript
|
Главная Библионтека Обобщенные функторы Эта глава посвящена обобщенным функторам (generalized functors) - мощной абстракции, позволяющей осуществлять несвязное взаимодействие между объектами (decoupled interobject communication). Обобщенные функторы особенно полезны, когда в объектах необходимо хранить запросы. Шаблон проектирования, описывающий инкапсулированные запросы, называется Command (Gamma et al., 1995). Кратко говоря, обобщенный функтор - это любой вызов процедуры, позволенный в языке С++, инкапсулированный в объекте первого класса, гарантирующем типовую безопасность (typesafe first-class object). (К первому классу относятся типы, обладающие всеми свойствами встроенных типов. Например, в языке С++ типы int и char являются типами первого класса. Объекты первого класса обладают полной семантикой значений. Их можно объявлять в любом месте программы, присваивать любым переменным, копировать и передавать по ссылке или по значению, а также возвращать в качестве значения функции. - Прим. ред.) Более детальное определение приводится ниже. • Обобщенный функтор инкапсулирует вызов любой процедуры, поскольку он получает указатели на простые функции, функции-члены, функторы и даже на другие обобщенные функторы, а также некоторые или все их аргументы. • Обобщенный функтор гарантирует типовую безопасность, поскольку он никогда не передает аргументы неверного типа неправильным функциям. • Обобщенный функтор является объектом, имеющим семантику значений, так как он полностью поддерживает копирование, присваивание и передачу параметров по значению. Обобщенный функтор можно свободно копировать, причем он не может содержать виртуальные функции-члены. Обобщенные функторы позволяют хранить вызовы процедур в виде значений, передавать их как параметры и выполнять далеко от места их создания. Они представляют собой усовершенствованный вариант указателей на функции. Существенное различие между указателями на функции и обобщенными функторами заключается в том, что функторы могут хранить состояние объекта и вызывать его функции-члены. В этой главе изложены следующие сведения. • Что такое шаблон проектирования Command и как он связан с обобщенными функторами. • В каких ситуациях они могут принести пользу. • Внутреннее устройство различных функциональных сущностей в языке С++ и способы их инкапсуляции с помощью единообразного интерфейса. • Как хранить вызов процедуры и некоторые или все ее аргументы внутри объекта, передавать ее другим объектам и свободно ее вызывать. • Как связывать между собой несколько отложенных вызовов и последовательно их выполнять. • Как использовать мощный шаблонный класс Functor, реализующий описанные выще функциональные возможности. 5.1. Шаблон Command в классической книге о шаблонах проектирования (Gamma et al., 1995) утверждается, что шаблон Command предназначен для инкапсулирования запроса внутри объекта. Объект этого класса представляет собой часть работы, хранящуюся отдельно от своего исполнителя. Общая структура шаблона Command представлена на рис. 5.1. Основной частью шаблона является сам класс Command. Его основное предназначение - ослабить зависимость между двумя частями системы: вызывающим модулем и получателем. Типичная последовательность действий такова. 1. Приложение (клиент) создает объект класса ConcreteCommand, передавая ему информацию, которой достаточно для выполнения задачи. Пунктирная линия на рис. 5.1 иллюстрирует тот факт, что клиент влияет на состояние объекта класса ConcreteCommand. 2. Приложение передает интерфейс Command объекта класса ConcreteCommand вызывающему объекту, который сохраняет этот интерфейс. 3. Позднее вызывающий объект выбирает момент для начала действий и активизирует виртуальную функцию-член Execute из класса Command. Механизм виртуальных вызовов переадресует этот вызов объекту класса ConcreteCommand, который отслеживает все детали. Объект класса ConcreteCommand связывается с объектом класса Receiver (это одно из его заданий) и использует его для выполнения реальной обработки данных, например, вызывая функцию-член Action. В противном случае объект класса ConcreteCommand может сам выполнить всю работу. В этом случае объект класса Receiver на рис. 5.1 не нужен. Client
Receiver ActionO ConcreteCommand state: Executed Рис. 5.1. Шаблон проектирования Command Вызывающий объект может свободно вызывать функцию Execute. Еще более важно то, что в ходе выполнения программы в вызывающий объект можно встроить разные действия, заменив объект класса Command, который в нем хранится. Следует отметить два момента. Во-первых, вызывающий модуль не знает, как выполняется работа. Это отнюдь не ново - чтобы использовать алгоритм сортировки, не обязательно знать, как именно он реализован. Однако вызывающий объект не знает даже, для какого вида обработки предназначен класс command. (Однако алгоритм сортировки обязан выполнять именно сортировку, а не что-то иное.) Вызывающий объект лищь вызывает функцию Execute из объекта класса Command при наступлении определенного события. С другой стороны, получатель не обязан знать, что его функция-член Action была вызвана каким-то объектом. Таким образом, объект класса Command гарантирует изоляцию вызывающего объекта от получателя вызова. Они могут быть абсолютно взаимно невидимыми, общаясь лищь через объект класса command. Обычно связи между вызывающим объектом и получателем вызова устанавливает объект класса Application. Это значит, что для заданного множества получателей можно использовать разные вызывающие модули, причем в заданный вызывающий модуль можно встраивать разных получателей - и все это происходит в условиях полной изоляции этих объектов. Во-вторых, посмотрите на щаблон Command в перспективе. В обычных программистских задачах, когда нужно выполнить какое-то действие, при вызове задаются объект, его функция-член и ее аргументы. window.ResizeCO, О, 200, 100); изменение размеров окна Момент инициализации такого вызова в принципе невозможно отличить от момента сбора его элементов в одно целое (объекта, процедуры и аргументов). Однако в щаблоне Command вызывающий объект уже содержит элементы вызова, откладывая сам вызов на неопределенное время. Это иллюстрируется следующим примером. Command resizeCmdC window, Объект &window::Resize, Функция-член О, О, 200, 100); Аргументы позднее ... resizeCmd.ExecuteC); изменение размера окна (Ниже мы остановимся на малоизвестной конструкции языка С++ &window::Resize.) В щаблоне Command момент сбора информации об окружении, необходимой для обработки данных, отличается от момента выполнения собственно обработки. В промежутке между этими двумя моментами программа хранит и передает запрос на обработку в виде объекта. Если бы не существовала возможность отложить вызов, не было бы и самого шаблона Command. С этой точки зрения само существование объекта класса Command обусловлено возможностью откладывания вызова, поскольку запрос в это время нужно где-то хранить. Отсюда следуют два важных аспекта щаблона Command. • Изоляция интерфейсов. Вызывающий объект изолирован от получателя вызова. • Разделение времени. Объект класса Command хранит готовый к выполнению отложенный запрос. Знание окружения также необходимо. Окружение (environment) точки выполнения - это множество сущностей (переменных и функций), которые являются видимыми из этой точки. В момент начала обработки необходимое окружение должно 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 |