Файл: 8. Переопределение методов. Полиморфные (виртуальные) методы.pdf
ВУЗ: Университет управления «ТИСБИ»
Категория: Учебное пособие
Дисциплина: Объектно-ориентированное программирование
Добавлен: 20.10.2018
Просмотров: 1352
Скачиваний: 10
конструктору и производится при создании объекта. Все объекты-
экземпляры одного класса связаны с одной и той же таблицей.
Например, пусть имеются два класса А и В, в каждом из которых
объявлено по два виртуальных метода. Пусть при выполнении программы
создано по два объекта этих классов. Тогда в памяти будут динамически
выделены области для самих объектов, их таблиц виртуальных методов и
кода этих методов. Схематично связи между этими областями можно
представить следующим образом.
Эта схема помогает понять, что происходит при выполнении
программы, когда объекты вызывают виртуальные методы. Пусть
выполнение программы дошло до точки, где объект 1 класса А вызывает
свой виртуальный метод 1. Тогда с помощью соответствующей таблицы
определяется размещение в памяти кода этого метода и вместо заглушки
происходит передача управления найденному фрагменту. Если же к
виртуальному методу 1 обращается объект класса В, то происходит
обращение к другой таблице и вызывается другой (альтернативный)
программный код. В следующем разделе эти общие концепции
иллюстрируются на примере иерархии графических объектов.
объектная
переменная 1
класса А
объектная
переменная 1
класса В
объектная
переменная 2
класса А
объектная
переменная 2
класса В
значения
свойств
объекта 1
класса В
адрес ТВМ
класса В
значения
свойств
объекта 1
класса А
адрес ТВМ
класса А
значения
свойств
объекта 2
класса А
адрес ТВМ
класса А
значения
свойств
объекта 2
класса В
адрес ТВМ
класса В
адрес ВМ 1
класса А
адрес ВМ 2
класса А
адрес ВМ 1
класса В
адрес ВМ 2
класса В
код ВМ 1 класса А
код ВМ 2 класса А
код ВМ 1 класса В
код ВМ 2 класса В
В завершение данного раздела опишем синтаксические приемы,
используемые для объявления виртуальных методов в разных языках. Здесь,
как ни странно, особняком стоит язык Java. Дело в том, что в языках
Delphi/FP, C#, и C++ по умолчанию методы считаются обычными, не
переопределяемыми, т.е. обрабатываемыми по схеме раннего связывания. А
вот в языке Java по умолчанию все методы считаются виртуальными, и
для них используется механизм динамической компоновки. Это необходимо
учитывать при объявлении методов в этих языках.
В языках C#, C++ и Delphi для объявления метода переопределяемым
в родительском классе в его заголовке надо поставить директиву virtual, а в
дочерних классах использовать либо директиву override (языки C# и
Delphi), либо опять же директиву virtual (язык C++).
TParentClass = class
procedure VirtMethod (параметры); virtual; // можно переопределять
procedure Method (параметры); // обычный (не переопределяемый)
end;
TChildClass = class (TParentClass)
procedure VirtMethod (параметры); override; // переопределяем
// метод Method просто наследуется и используется без изменений
procedure NewMethod (параметры); // новый обычный метод
procedure NewVirtMet (параметры); virtual; // новый виртуальный
end;
class ParentClass
{ virtual void VirtMethod (параметры); // можно переопределять
void Method (параметры); // обычный (не переопределяемый)
};
class ChildClass : ParentClass
{ override void VirtMethod (параметры); // переопределяем
// метод Method просто наследуется и используется без изменений
void NewMethod (параметры); // новый обычный метод
virtual void NewVirtMet (параметры); // новый виртуальный
};
В отличие от этих языков, в Java отключается механизм
переопределения, для чего используется директива final :
class ParentClass
{ void VirtMethod (параметры); // оставляем как переопределяемый
final void Method (параметры); // делаем не переопределяемым
};
class ChildClass extends ParentClass
{ void VirtMethod (параметры); // переопределяем
// метод Method просто наследуется и используется без изменений
final void NewMethod (параметры); // новый обычный метод
void NewVirtMet (параметры); // новый виртуальный
};
В качестве примера рассмотрим задачу разработки библиотеки классов
для графических объектов с использованием виртуальных методов. В
разделе 7 была разработана небольшая иерархия классов для графических
объектов (см. схему). В этой иерархии по вполне понятным причинам не
использовался механизм переопределения методов. Теперь же, после
изучения общих принципов переопределения, можно пересмотреть данную
иерархию с точки зрения использования в ней виртуальных методов.
Оставляя «за скобками» конструкторы и методы доступа, в числе
претендентов на виртуализацию остаются методы отображения и
перемещения, и здесь возникают достаточно интересные моменты. Метод
отображения уникален для каждого класса и поэтому в каждом классе
должен быть реализован своим программным кодом. А вот метод
перемещения алгоритмически одинаков: перемещение любой фигуры
можно описать тремя основными шагами:
стираем старое изображение за счет вызова метода прорисовки
изменяем координаты базовой точки фигуры
снова вызываем метод прорисовки
Ну а поскольку алгоритм перемещения одинаков, то зачем в каждом
классе повторять программный код метода перемещения? Разумно
выполнить реализацию этого метода один раз – в абстрактном классе
фигур и далее наследовать его во всех производных классах. Напомним,
что понятие абстрактного класса не запрещает включать в него
программный код некоторых методов. Следовательно, метод перемещения
не имеет смысла делать переопределяемым.
Идем дальше. Мы решили реализовать метод перемещения на «самом
верху» - в классе фигур. Но для программной реализации этого метода
необходимо вызывать метод отображения, а вот его в классе фигур нельзя
реализовать в принципе, можно включить в класс лишь его заголовок! Раз
нельзя – так нельзя, но заголовок-то метода есть, вот и будем в методе
перемещения «как бы вызывать» метод отображения, используя только его
заголовок! Тем самым метод перемещения оформляется как некая
заготовка настоящего полноценного метода, а для того чтобы все это
заработало, метод прорисовки надо объявить виртуальным и
переопределять в каждом производном классе.
В этом случае при обработке класса фигур компилятор/компоновщик
для метода перемещения создаст лишь часть программного кода, поскольку
в точках вызова метода отображения вместо реальных команд будут
вставлены заглушки. При обработке дочерних классов, в которых
переопределены методы прорисовки, компилятор/компоновщик для каждого
класса создаст таблицу виртуальных методов, которая во время
выполнения программы способна хранить адрес размещения в памяти
программного кода конкретной версии метода отображения. Это позволит
во время работы программы динамически настраивать метод
перемещения на работу с конкретным объектом, подставляя вместо
заглушек необходимый код отображения. Это и есть позднее связывание:
имя метода (в данном случае – метода отображения) заменяется
программным кодом лишь во время работы программы!
Теперь становится понятным, почему во всех классах методы
отображения должны иметь одинаковые имена (неважно – Show, Draw или
как-то еще, главное – одинаково!). Напомним еще раз, что одноименность
методов является обязательным условием использования механизма
переопределения. Это и есть полиморфизм методов – имя одно, а вариантов
программной реализации много (ну хотя бы два :)).
Итак, в иерархию классов для графических объектов надо внести
следующие изменения:
в
базовом
классе
фигур
метод
отображения
объявляем
виртуальным, оставляя его абстрактным
здесь же метод перемещения объявляем уже НЕ абстрактным, и
следовательно даем ему программную реализацию, в которую
вставляем вызовы абстрактного виртуального метода отображения
во всех производных классах убираем методы перемещения,
поскольку этот метод теперь может наследоваться из корневого
класса иерархии и с помощью механизма позднего связывания
динамически настраиваться на перемещение объекта любого класса
во всех производных классах переопределяем метод отображения и
даем ему конкретную программную реализацию с учетом специфики
объектов данного класса