Выбрать главу

create x

create x.make (...)

где вторая форма подразумевает и требует, чтобы базовый класс для типа T, приписанного x, содержал предложение creation, в котором make указана как одна из процедур-конструкторов. (Разумеется, процедура создания может иметь любое имя, - make рекомендуется по умолчанию). Результатом выполнения первой команды является создание нового объекта типа T, его инициализация значениями, заданными по умолчанию, и его присоединение к x. А при выполнении второй инструкции для создания и инициализации объекта будет вызываться make с заданными аргументами.

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

some_routine (...) is

local

u_temp: U

do

...; create u_temp.make (...); x := u_temp; ...

end

Это работает, но чересчур громоздко, особенно в контексте многозначного выбора, когда захочется присоединить x к экземпляру одного из нескольких возможных типов наследников. Локальные сущности (u_temp в нашем примере) играют только временную роль, их объявления и присваивания загромождают текст программы. Поэтому нужны специальные варианты конструкторов:

create {U} x

create {U} x.make (...)

Результат должен быть тот же, что и у конструкторов create, приведенных выше, но создаваемый объект должен являться прямым экземпляром U, а не T. Этот вариант должен удовлетворять очевидному ограничению: тип U должен быть согласован с типом T, а во второй форме make должна быть определена как процедура создания в классе, базовом для U, и если этот класс имеет одну или несколько процедур создания, то применима лишь вторая форма. Заметим, что здесь не важно, имеет ли сам класс T процедуры создания, - все зависит только от U.

Типичное применение связано с созданием экземпляра одного из нескольких возможных типов:

f: FIGURE

...

"Вывести значки фигур"

if chosen_icon = rectangle_icon then

create {RECTANGLE} f

elseif chosen_icon = circle_icon then

create {CIRCLE} f

else

...

end

Этот новый вид конструкторов объектов приводит к введению понятия тип при создании, обозначающего тип создаваемого объекта в момент его создания конструктором:

Для формы с неявным типом create x ... тип при создании есть тип x.

Для формы с явным типом create {U} x ... тип при создании есть U.

Динамическое связывание

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

Использование правильного варианта

Операции, определенные для всех вариантов многоугольников, могут реализовываться по-разному. Например, perimeter (периметр) имеет разные версии для общих многоугольников и для прямоугольников, назовем эти версии perimeterPOL и perimeterRECT. У класса SQUARE также будет свой вариант (умноженная на 4 длина стороны). При этом естественно возникает важный вопрос: что случится, если программа, имеющая разные версии, будет применена к полиморфной сущности?

Во фрагменте

create p.make (...); x := p.perimeter

ясно, что будет использована версия perimeterPOL. Точно так же во фрагменте

create r.make (...); x := r.perimeter

будет использована версия perimeterRECT. Но что, если полиморфная сущность p статически объявлена как многоугольник, а динамически ссылается на прямоугольник? Предположим, что нужно выполнить фрагмент:

create r.make (...)

p := r

x := p.perimeter

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

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

-- Вычислить периметр фигуры выбранной пользователем

p: POLYGON

...

if chosen_icon = rectangle_icon then

create {RECTANGLE} p.make (...)

elseif chosen_icon = triangle_icon then

create {TRIANGLE} p.make (...)

elseif

...

end

...

x := p.perimeter

или после условного полиморфного присваивания if ... then p := r elseif ... then p := t ..., ; или если p является элементом полиморфного массива многоугольников, или если p является формальным аргументом с объявленным типом POLYGON некоторой процедуры, которой вызвавшая ее процедура передала фактический аргумент согласованного типа?

Тогда в зависимости от хода вычисления динамическим типом p будет RECTANGLE, или TRIANGLE, или т.п. У нас нет никакого способа узнать, какой из этих случаев будет иметь место. Но, благодаря динамическому связыванию, этого и не нужно знать: что бы ни случилось с p, при вызове будет выполнен правильный вариант компонента perimeter.

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

Динамическое связывание позволяет завершить начатое выше обсуждение аспектов, связанных с потерей информации при полиморфизме. Сейчас стало понятно, почему не страшно потерять информацию об объекте: после присваивания p := q или вызова some_routine (q), в котором p являлся формальным аргументом, теряется специфическая информация о типе q, но если применяется операция p.polygon_feature, для которой polygon_feature имеет специальную версию, применимую к q, то будет выполняться именно эта версия.

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