Главная » SQL, Базы данных » ПОЛИМОРФИЗМ И ЗАМЕНЯЕМОСТЬ

0

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

Полиморфизм

На основании самого определения наследования можно сделать вывод, что если Т’ — подтип типа т, то все операторы, которые могут применяться к значениям типа т, являются также применимыми к значениям типа T’. Например, если  оператор AREA (е) является применимым по отношению к тому значению е, которое представляет собой эллипс, то применимым должен также быть оператор AREA (с), где с представляет собой окружность. Поэтому следует отметить, что необходимо тщательно учитывать логическое различие между формальными  параметрами, в терминах которых определен данный оператор (вместе с их объявленными типами), и соответствующими фактическими параметрами в конкретном вызове этого оператора (которые обладают своими наиболее конкретными типами). Например, оператор AREA определен в терминах  формальных параметров объявленного типа ELLIPSE (см. раздел 20.2), но наиболее конкретным типом фактического параметра в вызове AREA (с) является CIRCLE.

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

ТУРЕ ELLIPSE . . .

POSSREP { А …, В …, CTR … } ;

TYPE CIRCLE . . .

POSSREP { R . . ., CTR . . . } ;

3  В действительности, не существует каких-либо обоснованных причин, по которым все  значения одного и того же типа должны были бы иметь одно и то же физическое представление. Например, одни точки могут быть физически представлены с помощью декартовых координат, а  другие — с помощью полярных координат; одни температуры могут быть физически  представлены в градусах Цельсия, а другие — в градусах Фаренгейта; одни целые числа могут быть физически представлены в виде десятичных чисел, а другие — в виде двоичных и т.д.  Безусловно, в подобных случаях система должна иметь сведения о том, как преобразуются  физические представления, для того чтобы обладать способностью реализовывать операторы присваивания, сравнения и т.д. должным образом.

Поэтому допустимо применение двух разных версий оператора AREA,  скрытых от пользователя, в одной из которых используется возможное представление ELLIPSE, а в другой — возможное представление CIRCLE. Еще раз отметим, что это условие является допустимым, но не необходимым. Например, код этого оператора применительно к эллипсам может выглядеть следующим образом.

OPERATOR AREA ( E ELLIPSE ) RETURNS AREA ; RETURN ( 3.14159 * THE_A ( E ) * THE_B ( E

) ) ; END OPERATOR ;

(Площадь эллипса определяется по формуле πаb.) Вполне очевидно, что этот код работает правильно, если при вызове ему передается окружность вместо более общей фигуры — эллипса, поскольку применительно к окружности оба оператора ТНЕ_А И ТНЕ_В возвращают радиус r. Но программист, отвечающий за определение типа CIRCLE, может решить по многим причинам реализовать другую версию оператора AREA, которая относится только к окружностям и вызывает  оператор THE_R вместо операторов ТНЕ_А и ТНЕ_В.

Примечание. Действительно, в целях повышения эффективности, так или иначе было бы желательно реализовать две версии оператора, даже если возможные представления являются  одинаковыми.  Рассмотрим,  например,  многоугольники  и  прямоугольники. Алгоритм вычисления площади многоугольника общей формы, безусловно, будет действовать и применительно к прямоугольнику, но для вычисления площадей прямоугольников предусмотрен более эффективный алгоритм— умножение высоты на ширину.

Но следует отметить, что код для эллипсов, безусловно, будет не применим для окружностей, если он разработан в терминах физического представления типа ELLIPSE, a не возможного представления, и физические представления  типов  ELLIPSE И CIRCLE различаются. Подход к реализации операторов в  терминах физических представлений вообще не рекомендуется4. Поэтому код следует разрабатывать осмотрительно!

Так или иначе, но если оператор AREA не будет повторно реализован для  типа CIRCLE, то имеет место ситуация повторного использования кода (здесь речь идет о коде реализации оператора AREA).

Примечание. Более важная разновидность ситуации повторного использования кода встретится в следующем подразделе.

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

4 Наша собственная рекомендация фактически состоит в том, что доступ к физическим представлениям  должен  быть  ограничен  только  тем  кодом,  в   котором  реализуется операторы, предписанные моделью (селекторы, операторы ТНЕ_ и т.д.). (Более того, многие из этих операторов на практике, скорее всего, будут иметь реализации, предусмотренные в системе.)

Итак, идею полиморфизма как таковую нельзя назвать такой уж новой, как уже мог заметить читатель (фактически эта тема уже кратко рассматривалась в главе 5). Например, в языке SQL давно применяются полиморфные операторы ( "=", " + ", " | | " и многие другие) и фактически такая же ситуация обнаруживается в большинстве других языков. Некоторые языки даже позволяют пользователям  определять свои собственные полиморфные операторы; например, в языке PL/I такое средство предоставляется под именем "универсальных" функций (ключевое  слово GENERIC). НО  ВО  всех приведенных здесь примерах наследование как таковое отсутствует, поскольку все эти примеры относятся к так называемому полиморфизму перегрузки (overloading polymorphism). В отличие от этого, тот вид полиморфизма, который обнаруживается в операторе AREA, НОСИТ название полиморфизма включения (inclusion polymorphism) на том основании, что связь  между (скажем) окружностями и эллипсами по сути представляет собой отношение включения множеств [20.4]. По очевидным причинам до конца этой главы  неуточненный термин полиморфизм применяется для обозначения именно полиморфизма включения, если явно не указано иное.

Примечание. Ниже приведены наглядные определения, позволяющие лучше  понять различия между полиморфизмом перегрузки и полиморфизмом включения.

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

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

Программирование с использованием полиморфизма

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

FOR EACH x ∈

DIAGRAM CASE ;

WHEN IS_SQUARE ( X ) THEN CALL DISPLAY_SQUARE …

; WHEN IS_CIRCLE ( X ) THEN CALL DISPLAY_CIRCLE

… ;

END CASE ;

(Здесь предполагается, что предусмотрены операторы IS_SQUARE, IS_CIRCLE и т.д., которые могут применяться для проверки того, относится ли данное значение к указанному типу; см. раздел 20.6.) В отличие от этого, при использовании полиморфизма код становится более простым и намного более выразительным, как показано ниже.

FOR EACH X £ DIAGRAM CALL DISPLAY ( X ) ;

Пояснение. В этом примере DISPLAY— полиморфный оператор. Версия реализации оператора DISPLAY, предназначенная для работы со значениями типа т, задается при

определении типа Е и с  этого момента становится известной системе. Поэтому на этапе

прогона программы при обнаружении системой вызова оператора DISPLAY с фактическим параметром х система должна определить наиболее конкретный тип х, а затем вызвать версию оператора DISPLAY, соответствующего этому типу. Такой процесс называется связыванием на этапе прогона5 (run-time binding). Другими словами, полиморфизм по сути означает, что выражения CASE и операторы CASE, которые в ином случае должны были бы появиться в исходном коде пользовательской программы, становятся скрытыми от пользователя, поскольку эти операции выбора направления дальнейшего продолжения программы с помощью операторов CASE фактически выполняет система от имени пользователя.

В частности, заслуживает внимания то, какое воздействие оказывают описанные выше средства на весь процесс сопровождения программы. Например, предположим, что в качестве еще одного непосредственного подтипа  POLYGON определен новый тип TRIANGLE и поэтому на отображаемой схеме теперь будут появляться также и треугольники. Без использования полиморфизма каждую программу, которая содержит выражение CASE или другие операторы, аналогичные приведенным в данном примере, теперь придется модифицировать и включить в нее код примерно в следующей форме.

WHEN IS_TRIANGLE ( X ) THEN CALL DISPLAY_TRIANGLE … ;

Однако при использовании полиморфизма подобные модификации исходного кода не требуются.

Под впечатлением примеров, подобных приведенному выше, полиморфизм  иногда характеризуют таким метафорическим выражением, как "способ вызывать новый код с

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

Заменяемость

Как  уже  было  сказано,  понятие  заменяемости  по  сути  представляет  собой просто   понятие   полиморфизма,   рассматриваемое   немного   с   другой   точки зрения. Например, было показано, что если оператор AREA  (е), где е — эллипс, является допустимым, то должен быть также допустимым оператор AREA (с), где с — окружность. Иными словами, в  любом месте, где система допускает наличие эллипса,  можно  всегда  заменить  его  окружностью.  Более  общая  формулировка этого  принципа   состоит  в  том,  что  в  любом  месте,  где  система  допускает использование значения типа т, его всегда можно заменить значением  типа  T’, где  Т’  —  подтип  типа  т.  Этот  принцип  называется  принципом   заменяемости значений.

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

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

В частности, следует отметить, что из этого принципа следует такой вывод: если некоторое отношение r имеет атрибут А с объявленным типом ELLIPSE, то некоторые значения атрибута А в отношении r могут принадлежать к типу CIRCLE, а не просто к типу ELLIPSE. Аналогичным образом, если некоторый тип т имеет возможное представление, включающее компонент с объявленного типа ELLIPSE, то для некоторых значений v типа т вызов оператора ТНЕ_С (v) может возвращать значение типа CIRCLE, а не просто типа ELLIPSE.

Наконец, следует отметить, что заменяемость также является логическим продолжением наследования, поскольку понятие заменяемости в действительности представляет собой лишь другое определение понятия полиморфизма. Это означает, что если применяется наследование, то должна обеспечиваться заменяемость; в противном случае, не может быть и наследования.

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

По теме:

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