<<
>>

Сложный полиморфизм. Конструкторы

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

Пример 11.5. Разработать классы для реализации объекта Комната П, который должен отвечать на запрос о площади пола, выводя результат сразу на экран, и объекта Трехмерная комната П, который должен отвечать на запрос о площади стен и потолка, также выводя результат на экран (рис. 11.11).

Класс TRoom2 строим аналогично классу TRoom, добавив метод вывода результата на экран Print. Класс TVRoomP наследуем от TRoomP, переопределив метод определения площади и метод инициализации полей объекта (рис. 11.11).

В результате классы будут описаны следующим образом.

Вариант 1-с ошибкой!

Program ex;

Type TRoomP=object

length, width:real; {поля: длина и ширина комнаты} function Squaretreal; {метод определения площади} procedure Print;{метод, вывода результата на экран} procedure Init(l,w:real);{инициализирующий метод} end;

Function TRoomP.Square; {метод определения площади}

Begin

Square:-length* width;

End;

Procedure TRoomP.Print; {метод вывода результатов}

Begin

WriteLnCПлощадь =’, Square:6:2); {внутренний вызов метода} End;

Procedure TRoomP.Init; {тело инициализирующего метода}

Begin

length:-I; width: =w;

End;

Type TVRoomP - object(TRoomP)

height.real; {дополнительное поле класса}

function Squaretreal; {переопределенный метод класса} procedure Init(l,w,h:real); {переопределенный

инициализирующий метод}

end;

Procedure TVRoomP.Init;

Begin

inheritedInit(l,w);{инициализирует поля базового класса} height:=h; {инициализируем собственное поле класса}

End;

Function TVRoomP.Square;

Begin {обращаемся к методу базового класса}

Square:inherited Square + 2*height*(length + width);

End;

Var A'.TRoomP; B:TVRoomP; {объявляем объекты-переменные} Begin

A.Init(3.5,5.1); {инициализируем поля объекта A}

A.

Print; {выведет «Площадь = 17.85»}

B.

Init(3.5,5.1,2.7); {инициализируем поля объекта В}

B.Print; {выведет «Площадь = 17.85» - ошибка!!!}

End.

Класс TRoomP Класс TVRoomP

-*■■■ — Раннее связывание м Позднее связывание

Рис. 11.13. Необходимость позднего связывания

Ошибка возникла из-за того, что метод Print, который наследуется классом TVRoomP, вызывает метод Square. Метод Square в производном классе переопределяется, но метод Print ничего об этом не «знает» и по-прежнему вызывает метод Square класса TRoomP (см. пунктирные стрелки на рис. 11.13).

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

Для организации сложного полиморфизма необходимо:

1) переопределяемые методы описать служебным словом virtual;

2) к методам класса с виртуальными полиморфными методами добавить специальный метод-процедуру - конструктор, в котором служебное слово procedure заменено служебным словом constructor;

3) вызвать конструктор прежде, чем произойдет первое обращение к виртуальным полиморфным методам.

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

Адрес ТВМ хранится в объекте в специальном внутреннем, невидимом для программиста поле размером 2 байта (рис.

11.14). Запись адреса ТВМ в это поле происходит неявно при выполнении конструктора, поэтому попытки вызовов виртуальных полиморфных методов до выполнения конструктора приводят к ошибкам нарушения адресации и «зависанию» компьютера.
Программа

объекта методов

Рис. 11.14. Связь объекта с ТВМ

Как правило, в качестве конструктора используют метод инициализации полей, так как его обычно вызывают в начале работы с объектом. При повторном вызове конструктора никаких дополнительных действий не выполняется, метод работает как обычная процедура.

Пример 11.5. Продолжение. Исправим ошибку предыдущего варианта программы, объявив метод Square виртуальным полиморфным и используя метод Ink в качестве конструктора.

Вариант 2 -правильный

Program easel;

Type TRoomP-object

length, width: real; {поля : длина и ширина комнаты}

Junction Square.real; virtual; (метод определения площади} procedure Print; (метод вывода результата на экран} constructor Init(l,w:real); (конструктор}

end;

Function TRoomPSquare; (тело метода определения площади}

Begin

Square:= length* width;

End;

Procedure TRoomP.Print; (тело метода вывода результатов}

Begin

WriteLn(‘Площадь = Square: 6:2); (теперь вызов метода

происходит через ТВМ класса }

End;

Constructor TRoomP.lnit; (тело конструктора}

Begin

length: =1; width: =w;

End;

Type TVRoomP = object (TRoomP)

height.real; {дополнительное поле класса}

function Square:real; virtual; {виртуальный полиморфный метод} constructor Init(l, w,h:real); {конструктор} end;

Constructor TVRoomPInit;

Begin

inherited Init(l,w);{инициализируем поля базового класса} height:=h; {инициализируем собственное поле класса}

End;

Function TVRoomPSquare;

Begin

Square ^inherited Square+2*height*(length+ width);

End;

Var A:TRoomP; B.TVRoomP; {объявляем объекты-переменные}

Begin

A.Init(3.5,5.1); {конструируем объект A}

A.

Print; {выведет «Площадь = 17.85»}

B. Init(3.5,5.1,2.7); {конструируем объект В}

В.Print; {выведет «Площадь = 94.64» - верно!!!}

End.

Определены три случая, когда использование позднего связывания обязательно:

1) наследуемый метод для объекта производного класса вызывает метод, переопределенный в производном классе - пример такой ситуации рассмотрен выше;

2) объект производного класса через указатель базового класса обращается к методу, переопределенному производным классом;

3) процедура вызывает переопределенный метод для объекта производного класса, переданного в процедуру через параметр-переменную, описанный как объект базового класса (данную ситуацию часто называют «процедурой с полиморфным объектом»).

Примечание. При использовании параметров-значений аргумент-объект копируется. При этом копируется объект типа параметра, т.е. базового. Следовательно, передать в процедуру объект производного класса не удастся.

Собственно, все три случая сводятся к одному: обращение к полиморфному методу выполняется через указатель базового класса, которому может быть присвоен адрес объекта не только базового, но и производного класса. В теории программирования объект, адресуемый подобным указателем, получил название полиморфного.

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

Пример 11.6. Разработать классы для реализации двух динамических объектов: объект Комната Д должен отвечать на запрос о площади, объект Трехмерная комната Д должен отвечать на запрос о площади стен и потолка (рис.

11.15). Предусмотреть возможность обращения к полям и методам производного класса через указатель на базовый класс.

Между классами прослеживается отношение наследования, как в предыдущем примере, соответственно строим один на базе второго.

Иерархия классов приведена на рис.

11.16.

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

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

Unit RoomMod;

Interface

Type

TRoomD=object

length,width.real;(поля: длина и ширина

комнаты}

function Square:real; virtual; {метод определения площади}

constructor Init(l,w:real); {конструктор}

end;

Type TVRoomD = object(TRoomD)

height:real; {дополнительное поле класса}

function Square:real;virtual; {виртуальный полиморфный метод} constructor Initfl, w,h:real); {конструктор} end;

Implementation

Function TRoomD.Square;{тело метода определения площади}

Begin

Square:= length* width;

End;

Constructor TRoomD.Init; {тело конструктора}

Begin

length: =1; width: =w;

End;

Constructor TVRoomD.lnit;

Begin

inheritedInit(l,w);{инициализирует поля базового класса} height: =h; {инициализируем собственное поле класса}

End;

Function TVRoomD.Square;

Begin

Square: inherited Square+2*height*(length+ width);

End;

End.

Тогда основная программа будет выглядеть следующим образом.

Program case2;

Uses RoomMod;

Var pA: ЛTRoomD; {объявляем указатель на объекты класса} B'.TVRoomD; {объявляем объект класса} .

Begin

B.Init(3.5,5.1,2.7); {конструируем объект В}

WriteLn(‘Площадь = B.Square:6:2); {выведет «Площадь^ 94.64»} рА:=@В; {присваиваем указателю базового класса адрес объекта

производного класса}

WriteLn(‘Площадь = рА Л. Square:6:2); {выведет «Площадь= 94.64»} End.

Пример 11.7. Разработать классы для реализации объекта «Комната Д», который должен отвечать на запрос о площади, и объекта «Трехмерная комната Д», который должен отвечать на запрос о площади стен и потолка. Пре- дусмотретъ внешнюю процедуру вывода результатов, которая получает адрес объекта через параметр-переменную.

Если при разработке классов не объявить метод Square виртуальным полиморфным, то из процедуры Print и для аргумента - объекта базового класса, и для аргумента - объекта производного класса будет вызываться метод Square базового класса.

Program case3;

Uses RoomMod;

Procedure Print(Var R.'TRoomD); {процедура с полиморфным объектом}

Begin

WriteLn(‘faou{adb= ’, R.Square:6:2);

End;

Var A:TRoomD; B.TVRoomD; {объявляем объекты-переменные}

Begin

A. Init(3.5,5.1); {конструируем объект A}

B. Init(3.5,5.1,2.7); {конструируем объект В}

Print(A); {выведет «Площадь= 17.85»}

Print(B); {выведет «Площадь= 94.64»} ,

End.

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

TypeOf() '.pointer - возвращает адрес ТВМ класса. Если адреса ТВМ объекта и класса совпадают, то объект является переменной данного класса. Например:

ifTypeof(Self) = ТуреО/()

then else

При выборе механизма переопределения методов (с ранним или поздним связыванием) в тех случаях, когда это не вызвано необходимостью, следует помнить, что:

1) позднее связывание требует построения ТВМ, а следовательно больше памяти;

2) вызов виртуальных полиморфных методов происходит через ТВМ, а следовательно медленнее;

3) список параметров одноименных виртуальных полиморфных методов должен совпадать, а статических полиморфных - не обязательно;

4) статический полиморфный метод не может переопределить виртуальный полиморфный метод.

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

<< | >>
Источник: Иванова Г.С. Основы программирования. 2002

Еще по теме Сложный полиморфизм. Конструкторы:

  1. КОНСТРУКТОР ДВИГАТЕЛЕЙ
  2. Сложные общества
  3. Статья 188. Сложные вещи
  4. сложные умения
  5. 1.7. Сложные вопросы
  6. Глава 8 Просто о сложном
  7. Простая сложная работа
  8. ГЛАВА ТРЕТЬЯ СЛОЖНЫЕ И СОВОКУПНЫЕ ОБЯЗАТЕЛЬСТВА
  9. простые и сложные умения
  10. Сложные профессиональные умения
  11. Очень сложно обосновать
  12. А в интернет попасть не так уж сложно
  13. Слишком сложные вопросы для начала беседы.
  14. Обеспечение деятельности сотрудников правоохранительных органов в сложных условиях20.
  15. 7. Производительный труд – это сложнейший мир социальных влияний, противоречий, первые опыты подлинно гражданского поведения
  16. Проверка содержательности и достоверности информации является более сложным делом и требует другого аппарата и других методов проверки.
  17. 7. Каждый школьник – это сложнейший мир проблем и задач. Забота о своевременном решении этих проблем и задач составляет основу строительства новой школы
  18. § 9 Сложные и совокупные обязательства. – Соединение нескольких обязательств в одном договоре. – Условие о процентах. – Процен- ты по условию. – Проценты умедления. – Проценты процессу- альные. – Законная мера процентов. – Причисление процентов к капиталу. – Экономический спор о мере процентов. – Понятие о лихве. – Отмена законных ограничений и возражение против отмены. – Учет процентов. – Проценты текущего счета.
  19. Линейно-функциональные структуры.