дб. Четвертое издание джозеф Джарратано Университет Хьюстон клиэрЛэйк Гари Райли People5oft, Издательский дом "Вильямс" Москва СанктПетербург Киев 2007 ббк 32. 973. 26 018 75 Д
Скачать 3.73 Mb.
|
[ handler предназначена для удаления конструкции de йгпе я sage — handler. Функция get-defmesage-handler-list возвращает многозначное значение, содержащее список конструкций defmessage — handlers, которые относятся к указанному классу. Эти команды имеют следующий синтаксис: Глава 11. Классы, экземпляры и обработчики сообщений 890 В данном определении терм MAIN::RECTANGLE compute-area () (* (send ?self get-height) (send ?self get-width))) CLIPS> (undefmessage-handler RECTANGLE get- height)J [МЯОРЯВЗ] System message-handlers may not be modified. CLIPS> (undefmessage-handler RECTANGLE compute- area before)J [MSGFUN8] Unable to delete message-handler(s) from class RECTANGLE. CLIPS> (undefmessage-handler RECTANGLE compute-area primary)J CLIPS> (get-defmessage- handler-list CIRCLE)J (CIRCLE get-radius primary CIRCLE put — radius primary CIRCLE compute-area primary) CLIPS> 11.9 Доступ к слоту и создание обработчика Управление доступом к слоту может быть обеспечено с помощью атрибутов слота access и create-accessor. Атрибут access непосредственно ограничивает Следует отметить, что обработчики сообщений, определяемые системой, не подлежат удалению. Класс RECTANGLE не имеет обработчика сообщений типа before для экземпляра compute- area, поэтому не может быть удален. В этом классе имеется обработчик сообщений compute — area типа primary, удаление которого возможно, нов команде unde fmessage — handler не требуется задавать тип primary, поскольку по умолчанию удаляются именно те обработчики сообщений, которые имеют этот тип. Функция get-de fmessage — handler— list возвращает три значения для каждого обработчика сообщений класс, за которым закреплен этот обработчик сообщений (это имя класса будет отличаться от имени класса, переданного в функцию только если задано ключевое слово inherit), имя обработчика сообщений и тип обработчика сообщений. Доступ к слоту и создание обработчика 891 тип доступа, который допускается применять к слоту. Если этому атрибуту присвоено значение read-write, предусмотренное по умолчанию, то к этому слоту может быть непосредственно получен доступ для чтения или записи с помощью обработчиков класса, с использованием сокращенного обозначения слота ? яе1й: Единственный способ обеспечить передачу значения для сохранения в слоте — использовать атрибут, заданный по умолчанию (read — write). Задание значения атрибута доступа, равного initialize-only, аналогично применению атрибута read — only, за исключением того, что допускается возможность задавать значение при создании экземпляра (например, с помощью функции make — instance). Для управления автоматическим созданием обработчиков get — и put — для слотов класса используется атрибут create — accessor. Если этому атрибуту присвоено значение read-write, которое предусмотрено по умолчанию, то создаются оба обработчика — и put —. Аналогичным образом, если этому атрибуту присвоено значение read, то создаются только обработчики get —, а если присвоено значение write — только обработчики put —. Наконец, если этому атрибуту присвоено значение ? тоне создаются ни обработчики get —, ни обработчики put Некоторые комбинации атрибутов access и create — accessor явно вызывают ошибки например, это происходит, если атрибуту access для слота присваивается значение read — а атрибуту create — accessor — значение read — Очевидно, что не существует возможности выполнять запись в слот, если разрешено только чтение из этого слота. Рассмотрим пример, в котором демонстрируется применение этих атрибутов. Предположим, что от заказчика получен заказ и необходимо подсчитать его общую стоимость. Каждый заказ обозначается уникальным идентификатором, причем после присваивания этого идентификатора он не должен изменяться. Общая стоимость заказа рассчитывается как стоимость отдельных позиций в заказе, складывающаяся с налогом с оборота. Кроме того, желательно, чтобы общая стоимость вычислялась или задавалась в коде обработчика создаваемого класса. Код, который будет использоваться для разрабатываемого класса, предназначенного для вычисления общей стоимости заказа, приведен ниже. (defclass ORDER (а USER) (slot ID (access initialize-only) (default-dynamic (gensym))) (slot total-price (create- accessor read) (default 0.0)) 892 Глава 11. Классы, экземпляры и обработчики сообщений order-price (default 0.0)) (slot sales-tax (default 0.0))) (defmessage-handler ORDER compute-total-price () (bind ?self:total- price (* ?self:order-price (+ 1 ?self:sales-tax)))) Значение слота может быть задано только при создании экземпляра. Если же для этого слота значение останется незаданным, то необходимое значение будет вырабатываться динамически путем вызова функции gensym. Атрибуту create — accessor слота total — price присваивается значение read. Таким образом, обработчик get-total-price будет создаваться, а обработчик put — total — price — нет. Наконец, определяется обработчик compute- total — price, в котором вычисляется общая стоимость заказа путем сложения стоимости заказа с соответствующей суммой налога, начисляемой по указанной ставке налога с оборота. Например, если значение order — price равно 10. 00, а значение sales — tax составляет 0 ° 05, то значение total — price будет равно 10 . 50. Приведенный ниже диалог показывает, какие ограничения распространяются назначения слотов. CLIPS> (make-instance order1 of ORDER (ZD 4001) (order-price 10.00) (sales-tax 0.05)) [orderl] CLIPS> (send [order1] put-ID 4002) 1 [MSGFUN3] ID slot in [order1] of ORDER: write access denied. [PRCCODE4] Execution halted during the actions of message- handler put-ID primary in class ORDER FALSE CLIPS> Обратите внимание на то, что значение слоту ID может быть присвоено вовремя создания экземпляра orderl, нов дальнейшем присвоить это значение с помощью обработчика put — ID невозможно. Аналогичным образом, как показывает следующий диалог, может быть осуществлена выборка значения total-price с использованием обработчика get — total — price, а присвоить это значение с помощью обработчика put — total — price невозможно CLIPS> (send [order1] get-total-price)J 0.0 CLIPS> (send [order1] put-total-price 10.50)J 11.9. Доступ к слоту и создание обработчика 893 [MSGFUN1] No applicable primary message-handlers found for put-total-price. FALSE CLIPS> Для вычисления правильного значения стоимости заказа должен быть вызван обработчик compute — total — price: CLIPS> (send [order1] compute-total-price)J 10.5 CLIPS> (send [order1] get-total-price)J 10.5 CLIPS> Можно также ввести код обработчика get — total — price вручную, так, чтобы не приходилось явно вызывать обработчик compute — total — price для вычисления правильного значения общей стоимости. Если для определения обработчика get — класса используется атрибут create — accessor, то обработчик сообщений создается в следующей форме (defmessage-handler (default-dynamic (gensym))) (slot total-price (create-accessor ? NONE) (default 0.0)) (slot order-price (default 0.0)) (slot sales-tax (default 0.0))) (defmessage-handler ORDER get-total-price () (send ?self Глава 11. Классы, экземпляры и обработчики сообщений 894 (defmessage-handler ORDER get-total-price () (send ?self compute- total-price)) После этого, создав экземпляра затем запрашивая значение общей стоимости, можно каждый раз получать наиболее актуальное значение CLIPS> (make-instance order2 of ORDER (ID 4002) (order-price 20.00) (sales-tax 0.05)) [order21 CLIPS> (send [order2] get-total-price)J 21.0 CLIPS> Обработчики сообщений йоге, айзек и around (defclass а USER) (slot ID (access initialize-only)) (slot total-price (create- accessor read) (default 0.0)) (slot order-price (default 0.0)) (slot sales-tax (default 0.0))) (defmessage-handler ORDER compute- total-price () (bind ?self:total-price Действия, выполняемые классом, могут не соответствовать предъявляемым к нему требованиям, притом что возможность модифицировать код класса в соответствии с этими требованиями будет отсутствовать. Причиной этого часто бывает то, что от такого именно функционирования класса зависит работа другого кода. Еще одной причиной может стать недостаточное знакомство разработчика с кодом класса, что не позволяет модифицировать должным образом работу этого класса. В подобных случаях можно определить новый класс, наследующий от родительского класса все свойства, которые должны остаться неизменными, а затем предусмотреть в новом классе другие необходимые свойства, которые должны измениться. Еще раз рассмотрим код примера с определением класса ORDER и попытаемся создать новый класс, который будет гарантировать, что информация об общей стоимости заказа всегда остается актуальной 11.10. Обработчики сообщений before, after и around 895 (* ? self:order-price (+ 1 ?self:sales-tax)))) Затем необходимо определить новый класс, в котором требуется реализовать возможность выполнения особых действий. Назовем этот класс — ORDER: (defclass MY-ORDER (is à ORDER)) Один из подходов, который может быть принят при решении данной задачи, состоит в том, чтобы полностью переопределить обработчик сообщений get — total — price, предусмотрев в нем и новые, и прежние возможности (defmessage-handler MY-ORDER get-total-price () (send ?self compute-total-price) ?self:total-price) В данном случае перед возвратом значения слота total — price вызывается обработчик сообщений compute — total — price. Но при использовании такого подхода возникает целый ряд проблем. Во-первых, этот замысел просто неосуществим. Слот total-price имеет атрибут private, поэтому к нему невозможно получить доступ с помощью ссылки ?self из-за пределов класса. Кроме того, ссылку ? self: total — price невозможно заменить вызовом (send ? self get — total — price), поскольку это будет вызов обработчика MY — ORDER, а не ORDER. Во- вторых, еще один недостаток этого подхода связан с дублированием кода. В данном случае дублируется очень небольшой и несложный фрагмент кода, ? sel f: total — price, но предположим, что в действительности этот фрагмент весьма велик. Неважно даже то, что для дублирования кода в программе требуется дополнительное пространство. Гораздо важнее то, что после внесения изменений в первоначальный код обработчика ORDER новые свойства этого обработчика не будут унаследованы обработчиком MY — ORDER. Даже по одной этой причине не следует пытаться переопределять обработчик таким образом. Возможно также немного переопределить обработчик для получения требуемых свойств следующим образом MY-ORDER get-total-price () (send ?self compute-total-price) (call-next-handler)) Функция call-next-handler вызывает следующий обработчик сообщений, который был перекрыт текущим обработчиком сообщений. При вызове этой функции не требуется задавать какие-либо параметры. Следующему обработчику передаются параметры, с которыми был вызван текущий обработчик. Отслеживание работы обработчиков сообщений вовремя передачи сообщения get- Глава 11. Классы, экземпляры и обработчики сообщений 896 CLIPS> (make-instance order3 of MY-ORDER (ID 4003) (order-price 10.00) (sales-tax О. 05) ) 1 CLIPS> (watch message-handlers)J CLIPS> (send [огбегЗ] get-total-price)J HND » get-total-price primary in class MY-ORDER ED:1 ( compute-total-price primary in class ORDER ED:2 ( ( ORDER ED:1 ( если тип обработчика не указан, предусмотрена также возможность использовать обработчики типов before, after и around. Tun обработчика задается после имени обработчика, причем каждый класс может иметь обработчики каждого типа. Не удивительно, что тип обработчика сообщений before определяет обработчик сообщений, который должен быть вызван на выполнение перед обработчиком сообщений типа primary. Ниже приведен пример применения обработчика сообщений типа beгоге в классе MY — ORDER. (defmessage- handler MY-ORDER get-total-price before () (send ?self compute- total-price)) В отличие от рассматриваемого перед этим обработчика сообщений gettotal — price типа primary, в данном обработчике передается только сообщение compute — total — price, а функция call — next — handler не вызывается. price экземпляру MY — ORDER позволяет убедиться в том, что вызываются обработчики сообщений get — total — price обоих классов, MY — ORDER и ORDER: 11.10. Обработчики сообщений before, after и around 897 Вызов этой функции не требуется, поскольку так или иначе в первую очередь вызывается обработчик типа before класса MY — ORDER, а затем — обработчик типа primary класса Удалив ранее определенный обработчик типа primary класса MY — ORDER, в этом можно убедиться, отслеживая работу обработчиков сообщений следующим образом CLIPS> (make- instance order4 of MY-ORDER (ID 4004) (order-price 10.00) (sales- tax 0.05)) [order4] CLIPS> (watch message-handlers)J CLIPS> (send [order4] get-total-price)J HND » get-total-price before in class MY-ORDER ED:1 ( get-total-price before in class MY-ORDER ED:1 ( HND » get-total-price primary in class ORDER ED:1 ( ( ORDER, который вызывает обработчик сообщений compute — total-price типа primary класса ORDER, а затем осуществляется выход из обоих обработчиков. Наконец вызывается обработчик сообщений get — total- price типа primary класса который возвращает обновленное значение слота total — Вместо вычисления правильного значения total — price перед вызовом обработчика get — total — price можно также вычислить значение total — price после внесения изменений либо в слот sales — tax, либо в order — price. Эту задачу можно осуществить, определив следующие обработчики сообщений типа after: (defmessage-handler MY-ORDER put-sales-tax after (? value) 898 Глава 11. Классы, экземпляры и обработчики сообщений (if (numberp (send ?self get-order-price)) then (send ?self compute- total-price))) (defmessage-handler MY-ORDER put-order-price after (?value) (if (numberp (send ?self get-sales-tax)) then (send ?self compute-total-price))) В указанных обработчиках сообщений обработчик compute — total — price вызывается после вызова обработчиков put — sales — tax и put — order — price типа primary. В каждом из этих обработчиков необходимо проверить, определено ли должным образом (как числовое) значение другого слота, используемое в обработчике сообщений compute — total-price, поскольку в течение всего процесса создания экземпляра таким слотам не присваивается предусмотренное по умолчанию значение, равное О. О. В этом можно убедиться, отслеживая работу обработчиков сообщений после удаления ранее заданного обработчика get — total — price типа before: CLIPS> (make-instance order5 of MY-ORDER (ID 4005) (order-price 10.00) (sales-tax 0.05))J torder5] CLIPS> (send [order5] get-total- price)J 10.5 CLIPS> (watch message-handlers)J CLIPS> (send [order5] put-order-price 20.00)J HND » put-order-price primary in class ORDER ED:1 ( HND » get-sales-tax primary in class ORDER ED:2 ( ( ORDER ED:2 ( 11.10. Обработчики сообщений before, after и around 899 ED:2 ( ED:1 ( ( ED:1 ( связи стем, что приходится проверять наличие числовых значений в слотах order-price и sales — tax с помощью обработчиков сообщений типа after, программа становится громоздкой, поэтому рассмотрим другой способ решения этой задачи. Вначале удалим код проверки значений из обработчиков сообщений типа ай1ег: (defmessage-handler MY-ORDER put- sales-tax after (?value) (send ?self compute-total-price)) (defmessage-handler MY-ORDER put-order-price after (?value) (send ?self compute-total-price)) После создания экземпляра можно обнаружить, что значение слота total— price является актуальным. В этот момент работа обработчиков сообщений не отслеживалась, поскольку для нас не представляют интереса несколько сообщений, отправленных вовремя создания экземпляра. Изменив значение слота order — price путем вызова обработчика put — order — price, можно обнаружить, что вначале вызывается обработчик сообщений put-order — price типа primary класса ORDER, а затем осуществляется выход изданного обработчика. После этого вызывается обработчик сообщений put-order — price типа after класса MY — который затем вызывает обработчик сообщений get — sales — tax типа primary класса ORDER для определения того, присвоено ли слоту sales — tax соответствующее значение. Поскольку слот sales — tax имеет допустимое значение, вызывается обработчик compute-total — price типа primary класса ORDER для вычисления правильного значения стоимости, после чего происходит выход из обоих обработчиков compute-total — price типа primary и put-order — price типа after. В последующем для возврата правильного значения передается сообщение get — total — price. 900 Глава 11. Классы, экземпляры и обработчики сообщений Четвертым и последним типом обработчиков сообщений, предусмотренных в языке COOL, является обработчик сообщений типа around. Обработчики сообщений такого типа называют также оболочками, поскольку они охватывают своим кодом код обработчиков сообщений других типов, выполняемый дои после них. Обработчик сообщений типа around представляет собой своего рода комбинацию обработчиков типа before и айег, которая позволяет также аварийно прервать обработку сообщения в процессе передачи сообщения. Ниже приведен обработчик типа around для сообщения compute — total — price. (defmessage-handler MY-ORDER compute-total-price around () (if (or (not (numberp (send ?self get-order-price))) (not (numberp (send ?self get-sales-tax)))) then (return)) (call-next- handler)) Прежде всего в этом обработчике типа around проверяется, являются ли значения слотов order — price и sales — tax числовыми. В случае отрицательного результата этой проверки в данном обработчике происходит возврати не выполняются какие-либо иные действия. В этот момент не вызывается ни один из обработчиков типа before, primary или after для сообщения compute — total — price. Если значения обоих слотов, order — price и salestax, являются числовыми, то вызывается функция call — next — handler и вызываются другие обработчики сообщений. В данном случае должен быть вызван лишь обработчик сообщений compute — total-price типа primary класса ORDER. Это демонстрируется в приведенном ниже диалоге. Поскольку вывод становится довольно объемным, части трассировки действий, выполняемых обработчиком сообщений, чередуются с комментариями. CLIPS> (watch message-handlers)J CLIPS> (make-instance order5 of П) 4005) (order-price 10.00) (sales-tax 0.05)) ! HND » create primary in class USER ED:1 ( primary in class ORDER 11.10. Обработчики сообщений before, ай1ег и around 901 ED:1 ( ED:1 ( — instance создается экземпляр order5. Сразу после создания экземпляра вызывается обработчик сообщений create— еще один обработчик сообщений, определяемый системой. Вслед за созданием исходного экземпляра в слоты этого экземпляра помещаются значения, заданные в вызове функции make — instance. Вначале, как показано ниже, вызывается обработчик сообщений put — ID для сохранения значения «005 в слоте ID. HND » put-order-price primary in class ORDER ED:1 ( ( ORDER ED:1 ( « get-order-price primary in class ORDER ED:3 ( HND » get-sales-tax primary in class ORDER ED:3 ( ( ED:3 ( ORDER ED:3 ( обработчике типа around вызывается обработчик get — order — price типа primary класса ORDER для Глава 11. Классы, экземпляры и обработчики сообщений определения того, является ли значение слота order — price числовым (оно таковыми является, поскольку только что установлено равным 10 . О. Затем вызывается обработчик get — sales — tax типа primary класса ORDER для определения того, является ли значение слота sales — tax числовым. Если эта проверка оканчивается неудачей, в связи стем, что рассматриваемому слоту еще не было присвоено значение с помощью функции make — instance, то обработчик типа around снова вызывает обработчик get — order — price типа primary класса ORDER и возвращает требуемое значение. В таком случае обработчик computer — order — price типа around заканчивает свою работу, не вызывая обработчик типа Наконец осуществляется выход из обработчика put — order — price типа аЙег класса MY — ORDER, как показано ниже. HND » put-sales-tax primary in class ORDER ED:1 ( 0.05) HND « put-sales-tax primary in class ORDER ED:1 ( ( MY-ORDER ED:2 ( get-sales-tax primary in class ORDER ED:3 ( HND » compute-total-price primary in class ORDER ED:2 ( ORDER ED:2 ( tax типа primary класса ORDER для сохранения значения О. 05 в слоте sales — tax. Непосредственно после завершения работы этого обработчика вызывается обработчик put — sales — tax типа after класса MY — ORDER. Данный обработчик передает сообщение compute — total — price, в результате чего вызывается обработчик. Обработчики сообщений before, ай и around 903 compute — total — price типа around класса MY — ORDER. В этом обработчике типа around вызывается обработчик get — order — price типа primary класса ORDER для определения того, является ли значение слота order — price числовым. Оно таковыми является, поэтому затем вызывается обработчик getsales — tax типа primary класса ORDER для определения того, является ли числовым значение слота sales — tax (и оно является числовым, поскольку этому слоту было только что присвоено значение 0. 05). Теперь в обработчике типа around вызывается функция call — next — handler, которая, в свою очередь, вызывает обработчик compute — total — price типа primary класса ORDER, вычисляющий обновленное значение слота total — price. Наконец обработчик compute — total — price типа primary класса ORDER завершает свою работу, после чего происходит выход из обработчика сообщений compute-total— price типа around класса MY — ORDER, а затем — выход из обработчика putsales-tax типа after класса MY-ORDER: HND » init ED:1 HND « init ED:1 [order5] CLIPS> primary in class USER ( Обработчик сообщений init — это еще один обработчик сообщений, определяемый системой. Он вызывается в конце процесса создания экземпляра, после инициализации значений слотов. Перекрытие параметров обработчика сообщений В системе CLIPS предусмотрена возможность перекрывать параметры, передаваемые обработчику сообщений, с помощью команды override-next-handler. Эта команда имеет следующий синтаксис (override-next-handler Для ознакомления с примером использования этой команды рассмотрим, как можно было бы обрабатывать заказы на товары в иностранной валюте. Снова предположим, что по различным причинам нежелательно непосредственно модифицировать класс ORDER. Если значение order-price в экземпляре задано в долларах США, то можно предусмотреть специальную обработку информации с учетом стоимости, представлен 904 Глава 11. Классы, экземпляры и обработчики сообщений ной в долларах. Например, за доставку заказов со стоимостью больше 100 долларов может не взиматься оплата за доставку. Один из подходов к обработке данных о стоимости, представленных в другой валюте, мог бы состоять в том, чтобы определить класс, который наследует свои свойства от класса, но обеспечивает автоматическое преобразование стоимостей, заданных в иностранной валюте ив долларах США. Такой подход реализован в следующей конструкции defclass с именем FOREIGN — ORDER ив связанных с ней обработчиках FOREIGN-ORDER (а ORDER) (slot exchange-rate (default 1.0))) (defmessage-handler FOREIGN-ORDER get-order- price around () (* ?self:exchange-rate (call-next-handler))) |