Главная » SQL, Базы данных » ОПЕРАТОРЫ, ВЕРСИИ И СИГНАТУРЫ

0

Как было отмечено в разделе 20.3, каждый конкретный оператор может иметь много разных версий реализации, которые скрыты от пользователя. Это означает, что по мере продвижения по пути от некоторого супертипа т к некоторому подтипу T’ в иерархии типов необходимо иметь (по многим причинам), по меньшей мере, право на повторную реализацию операторов типа т для типа T’. В качестве примера рассмотрим следующий оператор.

OPERATOR MOVE ( E ELLIPSE, R RECTANGLE ) RETURNS ELLIPSE VERSION ER_MOVE ;

RETURN ( ELLIPSE ( THE_A ( E ) , THE_B ( E ) ,

R_CTR ( R ) ) )

; END OPERATOR ;

Оператор MOVE, неформально выражаясь, "передвигает" эллипс Е таким  образом, чтобы центр его совпадал с центром прямоугольника R или, точнее, он возвращает такой же эллипс, как и эллипс, заданный в виде фактического  параметра, соответствующего формальному параметру Е, за исключением того, что центр его находится в центре прямоугольника,  заданного  в  качестве  фактического  параметра,  соответствующего  формальному параметру R. Обратите внимание на спецификацию версии VERSION во второй строке, в которой вводится различимое имя ER_MOVE для данной конкретной версии MOVE (вскоре будет определена еще одна версия этого оператора). Следует также отметить, что подразумевается наличие оператора R_CTR, который возвращает  координаты центра указанного прямоугольника.

Теперь определим еще одну версию оператора MOVE, предназначенную для перемещения окружностей, а не эллипсов10, которая приведена ниже.

OPERATOR MOVE ( С CIRCLE, R RECTANGLE ) RETURNS CIRCLE VERSION CR_MOVE ;

RETURN ( CIRCLE ( THE_R ( С ), R_CTR ( R ) ) )

; END OPERATOR ;

Аналогичным образом может быть определена версия оператора MOVE для того случая, когда фактические параметры относятся, соответственно, к наиболее  конкретным типам ELLIPSE и SQUARE (скажем, ES_MOVE), и еще одну версию для того случая, когда фактические  параметры,  соответственно,  относятся  к  наиболее  конкретным  типам CIRCLE И SQUARE (скажем, CS_MOVE).

10 В данном конкретном примере определение такой версии фактически имеет мало смысла (объясните,

почему).

Сигнатуры

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

а)  фактические параметры и формальные параметры;

б)  объявленный тип и наиболее конкретный тип;

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

Фактически можно провести различие между по меньшей мере тремя разновидностями сигнатур, которые связаны с любым конкретным оператором Ор (хотя в литературе такое различие часто не учитывается!), — уникальная  сигнатура спецификации (specification signature), множество сигнатур версий (version signature) и множество сигнатур вызовов (invocation signature). Эти три разновидности сигнатур подробно описаны ниже.

■     Уникальная сигнатура спецификации состоит из имени оператора Ор наряду с объявленными типами, взятыми в том же порядке, что и формальные параметры оператора Oр, которые заданы пользователем в объявлении оператора Ор. Такая сигнатура соответствует оператору Ор, рассматриваемому в том виде, в каком он выглядит с точки зрения пользователя. Например, сигнатурой спецификации для  приведенного выше оператора MOVE является  просто MOVE (ELLIPSE, RECTANGLE).

Примечание. В [3.3] изложено предложение, чтобы была предусмотрена возможность отделить определение сигнатуры спецификации для любого  конкретного оператора от определений всех версий реализации этого оператора. Основная идея состоит в том, что должна быть обеспечена поддержка объединенных типов (иногда называемых также абстрактными  или неконкретизируемыми типами, а иногда просто интерфейсами); под этим подразумеваются типы, которые вообще не могут стать наиболее  конкретным типом любого значения. Такой тип предоставляет способ  определения операторов, применяемых к нескольким разным обычным типам, притом что все они являются строгими подтипами рассматриваемого объединенного типа. В таком случае версии реализации такого оператора могут быть определены для каждого из этих обычных подтипов. Если речь  идет о примере, который рассматривается на протяжении всей данной главы, то в этом смысле в качестве объединенного типа вполне может рассматриваться PLANE_FIGURE; в таком случае сигнатура спецификации оператора AREA вполне может быть определена на уровне типа PLANE_FIGURE, ЧТО дает возможность после этого определить явные версии реализации для типа ELLIPSE, типа POLYGON и т.д.

■     Каждая версия реализации оператора Ор имеет свою собственную сигнатуру версии, состоящую из имени оператора Ор наряду со взятыми в том же порядке объявленными типами формальных параметров, которые определены для данной

Глава 20. Наследование типов     797

версии. Эти сигнатуры соответствуют различным фрагментам кода реализации, в котором реализованы версии оператора Ор, скрытые от  пользователя. Например, сигнатурой   версии   для   версии   CR_MOVE   оператора   MOVE   должна   быть MOVE(CIRCLE, RECTANGLE).

Каждое возможное сочетание наиболее конкретных типов фактических  параметров имеет свою собственную сигнатуру вызова, состоящую из имени оператора Ор наряду со взятыми в том же порядке наиболее конкретными типами фактических параметров. Эти сигнатуры соответствуют всем возможным вызовам оператора Ор (безусловно, что это соответствие относится к типу "один ко многим"; это означает, что одна сигнатура вызова может соответствовать многим фактическим вызовам). Например,  предположим, что переменные Е И R имеют, соответственно, наиболее  конкретные типы CIRCLE и SQUARE. Тогда сигнатурой вызова для  вызова MOVE (Е, R) оператора MOVE является MOVE (CIRCLE ,  SQUARE ).

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

Кстати, следует отметить, что, во-первых, сигнатуры спецификации в действительности представляют собой понятие уровня модели; во-вторых, сигнатуры версии — это понятие уровня реализации; в-третьих, сигнатуры вызова, хотя и представляющие собой в

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

Сравнение операторов, обеспечивающих только чтение, и операторов обновления

Вплоть до этого момента рассматриваемый в данной главе оператор MOVE принадлежал к типу именно такого оператора, который обеспечивает только чтение. Но предположим, что данный оператор вместо этого необходимо сделать оператором обновления, как показано ниже.

OPERATOR MOVE ( E ELLIPSE, R RECTANGLE ) UPDATES E VERSION ER_MOVE ;

THE_CTR ( E ) := R_CTR ( R

) ; END OPERATOR ;

(Следует напомнить, что операторы, обеспечивающие только чтение, и  операторы обновления  иногда  именуются,  соответственно,  наблюдателями  и   модификаторами. Дополнительные сведения о том, в чем состоят различия между этими разновидностями операторов, приведены в главе 5.)

Теперь отметим, что вызов этой версии оператора MOVE приводит к  обновлению  его первого фактического параметра (неформально выражаясь, в  результате этого вызова "изменяется центр" объекта, заданного с помощью  данного фактического параметра). Отметим также, что эта операция обновления  действует успешно независимо от того, принадлежит  ли  этот  первый  фактический  параметр  к  наиболее конкретному  типу ELLIPSE или к наиболее конкретному типу CIRCLE; иными словами, явно заданная версия реализации для окружностей больше не требуется". Поэтому одним из преимуществ операторов  обновления в целом является то, что они позволяют исключить необходимость явно разрабатывать определенные версии реализации. Обратите внимание на то, какое значение это имеет, в частности, для сопровождения программ;  например,  что произойдет, если в дальнейшем будет введен тип O_CIRCLE как подтип CIRCLE? (Ответ. Вызов оператора MOVE с использованием в качестве фактического параметра переменной с объявленным типом ELLIPSE или CIRCLE, но с текущим наиболее конкретным типом O_CIRCLE, будет выполнен вполне успешно. Но в общем вызов этого оператора с использованием в качестве  фактического параметра переменной с объявленным типом O_CIRCLE не будет выполнен успешно.)

Изменение семантики оператора

Тот факт, что всегда, по меньшей мере, допустимой является повторная реализация операторов по ходу продвижения вниз по иерархии типов, имеет одно очень важное следствие — он открывает возможность изменения семантики рассматриваемого оператора. Например, в случае оператора AREA может оказаться, что реализация для типа CIRCLE фактически возвращает, скажем, периметр рассматриваемой окружности вместо площади. (Тщательное проектирование типа позволяет в определенной степени смягчить остроту этой проблемы; например, если оператор AREA определен как возвращающий результат типа AREA, то, безусловно, данная реализация не может вместо этого возвращать результат типа LENGTH. Но эта реализация все равно будет способна возвращать неправильное значение площади!)

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

шоссе)— строгий подтип типа HIGHWAY (Шоссе), a TRAVEL_TIME (Время  проезда) —

оператор, который вычисляет количество времени, необходимое для проезда между двумя указанными пунктами на указанном шоссе. Для платного шоссе эта формула выглядит как (d/s) + (n*t), где d — расстояние, s — скорость, п — количество пунктов сбора платы за проезд и t — время, проводимое в каждом пункте сбора. В отличие от этого, для бесплатного шоссе эта формула выглядит просто как d/s. Изменение семантики позволяет использовать один и тот же оператор TRAVEL_TIME для шоссе обоих типов.

В качестве контрпримера (т.е. примера ситуации, в которой изменение семантики, безусловно, является нежелательным) еще раз рассмотрим эллипсы и окружности. Предположим, что оператор AREA должен быть определен таким образом, чтобы каждая конкретная

11  Как было отмечено в сноске 10, такая версия фактически не требовалась и в случае  оператора, обеспечивающего только чтение; она была приведена исключительно для  использования в рассматриваемом примере.

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

1.  Определены тип ELLIPSE и соответствующая версия оператора AREA. Для упро щения предположим, что в коде AREA не используется физическое представление для эллипсов.

2.  Определен тип CIRCLE как подтип типа ELLIPSE, но (еще) не определена отдель ная версия реализации оператора AREA для окружностей.

3.  Вызывается оператор AREA с указанием некоторой конкретной окружности с для получения результата, скажем, al. В этом вызове используется версия оператора AREA, предназначенная для типа ELLIPSE (поскольку это — единственная версия, которая существует в настоящее время).

4.  Затем будет определена отдельная версия реализации оператора AREA для окруж ностей.

5.  Снова вызывается оператор AREA С указанием той же конкретной окружности с для получения результата, скажем, а2 (и на этот раз вызывается именно та версия оператора AREA, которая относится к типу CIRCLE).

В данный момент, безусловно, желательно, чтобы соблюдалось условие а2  = al. Но такому необязательному пожеланию нельзя придать силу закона; это означает, что (как уже было сказано выше) всегда существует такая возможность, что версия оператора AREA, реализованная для использования с окружностями,  может возвращать (скажем) периметр вместо площади или просто неправильное значение площади.

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

■     Если тип TOLL_HIGHWAY действительно является подтипом HIGHWAY, это по оп ределению означает, что каждое отдельное платное шоссе фактически представля ет собой просто шоссе.

■     Поэтому некоторые шоссе (т.е. некоторые значения типа HIGHWAY) действительно представляют собой платные шоссе — на них установлены пункты сбора платы за проезд. Поэтому сам тип HIGHWAY нельзя назвать типом, который описывает "шоссе без пунктов сбора платы за проезд"; он описывает "шоссе с п пунктами сбора платы за проезд" (где п может быть равно нулю).

■     Поэтому оператор TRAVEL_TIME для типа HIGHWAY не "вычисляет время проезда по шоссе без пунктов сбора платы за проезд", а " вычисляет время проезда d/s по шоссе без учета пунктов сбора платы за проезд".

■     В отличие от него, оператор TRAVEL_TIME ДЛЯ типа TOLL_HIGHWAY предназначен для "вычисления времени проезда (d/s)   +   (n*t) по шоссе с учетом пунктов сбора платы за проезд". Поэтому на самом деле два оператора TRAVEL_TIME ЯВ ЛЯЮТСЯ логически разными! В данном случае путаница возникла из-за того, что двум разным операторам присвоено одно и то же имя; фактически в данном случае

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

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

Источник: Дейт К. Дж., Введение в системы баз данных, 8-е издание.: Пер. с англ. — М.: Издательский дом «Вильямс», 2005. — 1328 с.: ил. — Парал. тит. англ.

По теме:

  • Комментарии