Скачиваний:
82
Добавлен:
02.05.2014
Размер:
2.28 Mб
Скачать

19.3. Полиморфизм и заменимость

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

Полиморфизм

Из самого понятия наследования следует, что если Т' — это подтип типа Т, то все операторы, которые применимы к значениям типа Т, применимы и к значениям типа Т'. Например, если оператор AREA(e) является допустимым, где е— некоторый эллипс, то оператор AREA(c), где с— некоторая окружность, также должен быть допустимым. За- метим, что необходимо соблюдать аккуратность в отношении отличия параметров в смысле их типов, объявленных в определении оператора, и соответствующих аргумен- тов с действительными (конкретными) типами, использованных при вызове этого опе- ратора. Например, оператор AREA определен с параметром, объявленным относящимся к типу ELLIPSE (см. раздел 19.2), а действительный (конкретный) тип аргумента в обраще- нии к оператору AREA( с) — имеющим тип CIRCLE.

Действительно, нет никакой логической необходимости даже в том, чтобы все значения одного и того же типа имели одно и то же реальное представление. Например, одни точки мо- гут быть представлены в декартовой системе координат, а другие — в полярной системе коор- динат; одни значения температуры могут быть представлены в градусах по Цельсию, а дру- гие — в градусах по Фаренгейту; одни целые числа могут быть представлены как десятичные, а другие — как двоичные и т.д. (Конечно, системе должно быть известно, как преобразовывать реальные представления во всех таких случаях, чтобы можно было правильно выполнять опера- ции присвоения, сравнения и т.д.)

Напомним, что эллипсы и окружности (по крайней мере, как мы их определили в раз- деле 19.2) имеют различные представления.

TYPE ELLIPSE POSSREP ( A LENGTH, В LENGTH, CTR POINT )... ; TYPE CIRCLE POSSREP ( R LENGTH, CTR POINT )... ;

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

OPERATOR AREA ( Е ELLIPSE ) RETURNS j AREA ) ;

RETURN ( 3.14159 * THE_A ( E ) * THE_B ( E ) ) ; END OPERATOR ;

(Площадь эллипса равна Jtab.) Этот код, очевидно, дает правильный результат и в том случае, если вызывается для вычисления площади окружности, а не эллипса. Операторы ТНЕ_А и ТНЕ_В возвращают радиус г. Однако программист, ответственный за определе- ние типа CIRCLE, может по разным соображениям предпочесть реализацию отдельной версии оператора AREA, которая предназначена конкретно для окружностей и в которой вызывается оператор THE_R вместо операторов ТНЕ_А и ТНЕ_В.

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

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

В случае, если код оператора AREA, используемый для типа ELLIPSE, не нужно пере- писывать заново для типа CIRCLE, можно говорить о повторном использовании кода (т.е. кода, реализующего оператор AREA).

Замечание. В следующем подразделе речь пойдет о более важном виде повторного использования.

С точки зрения модели, конечно, не имеет никакого значения, сколько версий оператора AREA неявно реализовано (что касается пользователя, то для него есть просто один оператор AREA, который применим для эллипсов, а следовательно, по определению — и для окруж- ностей). Иными словами, с точки зрения модели оператор AREA— полиморфный, т.е. при разных обращениях к нему он может принимать разные типы аргументов. Особо отметим, что такой полиморфизм является логическим следствием наследования: если есть наследо- вание, то должен быть и полиморфизм, в противном случае не будет и наследования!

Сама по себе идея полиморфизма не нова, как вы уже, по-видимому, поняли. Напри- мер, в языке SQL есть полиморфные операторы ("=", "+", "| |" и многие другие); есть та- кие операторы и в большинстве других языков программирования. В некоторых языках

пользователям даже разрешается вводить собственные полиморфные операторы. Напри- мер, в языке PL/I эта возможность предоставляется с помощью GENERIC-функций. Одна- ко наследования как такового ни в одном из данных примеров нет. Все это примеры так называемого перегружаемого полиморфизма. Вид полиморфизма, демонстрируемый с помощью оператора AREA, называют включаемым полиморфизмом на том основании, что связь между, например, окружностями и эллипсами, по сути, равносильна включению множеств [19.3]. По вполне понятным причинам далее в этой главе не уточненный до- полнительно термин "полиморфизм" будет использоваться для включаемого полимор- физма (кроме тех случаев, когда явным образом будет указано противное).

Замечание. Приведенные ниже дополнительные разъяснения помогут вам лучше по- нять различия между перегружаемым и включаемым полиморфизмом.

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

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

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

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

FOR EACH х IN DIAGRAM CASE ;

WHEN IS_SQ0ARE ( X ) THEN CALL DISPLAYJQUARE ... ; WHEN IS CIRCLE ( x ) THEN CALL DISPLAY CIRCLE ... ;

END CASE }

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

FOR EACH X in DIAGRAM CALL DISPLAY ( X ) ;

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

называют связыванием во время выполнения или поздним связыванием*. Иначе гово- ря, полиморфизм фактически подразумевает, что выражения и операторы CASE, которые в противном случае должны были бы присутствовать в коде пользователя, будут скрыты от него — система самостоятельно выполняет операции CASE, освобождая пользователя от рутинной работы.

Перечислим некоторые следствия вышесказанного, в частности в отношении сопро- вождения программ. Предположим, например, что новый тип TRIANGLE (Треугольник) определен как еще один непосредственный подтип типа POLYGON, и, следовательно, ото- бражаемая диаграмма теперь может содержать еще и треугольники. Если не использо- вать полиморфизм, то в каждую программу, которая содержит выражения или операто- ры CASE, подобные приведенному выше, потребуется добавить код следующего вида.

WHEN IS_TRIANGLE ( X ) THEN CALL DISPLAY_TRIANGLE ... ;

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

Исходя из примеров, подобных приведенному выше, полиморфизм иногда образно характеризуют как метод, "позволяющий старому коду вызывать новый код", т.е. благо- даря ему программа Р фактически сможет вызывать определенную версию определенно- го оператора, которой даже не существовало на момент написания этой программы Р. Таким образом, здесь мы имеем другой и более важный случай — пример повторного использования кода: одна и та же программа Р может быть пригодна для данных типа Т, которого, повторяем, даже не существовало на момент написания программы Р.

Заменимость

Как указывалось ранее, понятие заменимости на самом деле является просто тем же понятием полиморфизма, которое рассматривается с несколько иной точки зрения. Мы уже видели, например, что если оператор AREA{e), где е— эллипс, является допусти- мым, то оператор AREA(c), где с — окружность, также должен быть допустимым. Иными словами, везде, где система подразумевает эллипс, его всегда можно заменить окружно- стью. Или в общем виде это можно сформулировать так: везде, где системой подразуме- вается некоторое значение типа Т, его всегда можно заменить значением типа Т', где Т' — подтип типа Т. Это утверждение называют принципом заменимости значения.

Отметим, в частности, что из данного принципа следует, что если некоторое отноше- ние г имеет атрибут А, объявленный как атрибут типа ELLIPSE, то некоторые значения атрибута А могут иметь тип CIRCLE, а не именно тип ELLIPSE. Точно так, если некоторый тип Т имеет возможное представление, которое включает какой-то компонент С, объяв- ленный относящимся к типу ELLIPSE, то для некоторых значений v типа Т вызов опера- тора THE С{ v) может возвращать значение типа CIRCLE, а не типа ELLIPSE.

4 Связывание во время выполнения Относится, конечно, к вопросам реализации, а не к модели Этот вопрос, в отличие от других вопросов реализации, рассматривается здесь дополнительно, только для того, чтобы упростить правильное понимание концепции наследования в целом.

И наконец заметим, что, поскольку заменимость — это просто полиморфизм в дру- гом обличье, она также является логическим следствием наследования. Если есть насле- дование, то должна быть и заменимость; в противном случае не будет и наследования.

Соседние файлы в папке Дейт К. Дж. Введение в системы баз данных [7 издание]