ВУЗ: Университет управления «ТИСБИ»
Категория: Учебное пособие
Дисциплина: Объектно-ориентированное программирование
Добавлен: 20.10.2018
Просмотров: 932
Скачиваний: 15
9. Полиморфизм на уровне объектных переменных
Как известно, объектные переменные являются скрытыми указателями
(ссылками) на размещаемые в памяти объекты. Классический механизм
указателей в обычных языках предполагает строгое соответствие типов
указателей и адресуемых данных: переменные-указатели могут адресовать
только данные тех типов, с которыми они были связаны при объявлении.
В объектных языках это правило строгого соответствия типов немного
ослаблено: при определенных условиях объектные переменные могут
адресовать (ссылаться на) не только свои «родные» объекты, но и объекты
некоторых других классов. Более конкретно:
Пусть объектная переменная связана при объявлении с некоторым
классом. Тогда она может использоваться для доступа не только к
объектам этого класса, но и к объектам любых классов, производных от
данного.
Пусть, например, для иерархии графических фигур объявлены
следующие объектные переменные:
Fig : TFigure; // хоть класс фигур и является абстрактным,
// разрешается объявлять переменную такого классового типа,
// нельзя лишь использовать ее вместе с конструктором
Circ : TCircle; // доступ к объектам-окружностям
Ellipse : TEllipse; // доступ к объектам-эллипсам
Rect : TRect; // доступ к объектам-прямоугольникам
Пусть также с помощью конструкторов созданы соответствующие
объекты (окружность, эллипс, прямоугольник).
Тогда разрешается делать следующие присваивания:
Fig := Circ; // переменная Fig адресует окружность
Fig := Ellipse; // переменная Fig адресует эллипс
Fig := Rect; // переменная Fig адресует прямоугольник
Circ := Ellipse; // переменная Circ адресует эллипс
Однако недопустимыми будут следующие присваивания:
Circ := Fig; Rect := Fig; Circ := Rect; Ellipse := Circ;
Отсюда следует, что механизм полиморфности указателей работает
только в рамках единой иерархии классов и только вниз по иерархии от
заявленного базового класса.
А что будет, если объявить переменную классового типа
Object/TObject? Учитывая особое положение этого класса как корневого для
всей иерархии стандартных и нестандартных классов, получим, что такая
переменная сможет адресовать объекты абсолютно любых классов!
Очень мощным использованием полиморфных объектных переменных
является возможность объединения их в единую структуру. Это позволяет
собирать в один массив или список и циклически обрабатывать объекты
разных классов.
Например, можно создать массив указателей на любые графические
объекты в рамках единой иерархии графических фигур с корневым классом
TFigure/Figure:
var Figs : array [1..N] of TFigure;
Figure [ ] Figs = new Figure [ N ];
Пример заполнения массива ссылками на конкретные фигуры:
Figs[1] := TCircle.Create(…);
Figs[2] := TRect.Create(…);
. . . . . . . . . . . . . . . . . . . .
Figs[0] = new Circle(…);
Figs[1] = new Rect(…);
. . . . . . . . . . . . . . . . . . . .
Схематичное представление такого массива :
Подобный массив очень удобно можно обрабатывать с помощью
циклов. Например — показать все фигуры:
for i := 1 to count do Figs[ I ].Show;
for (int i=0; i < count; i++) { Figs[ i ].Show( ); }
Интересно, что в этих примерах используются оба проявления принципа
полиморфизма – как полиморфность объектных переменных, так и
виртуальные переопределяемые методы: цикл идет по массиву, для
каждого элемента происходит обращение к соответствующему объекту, для
которого с помощью его таблицы виртуальных методов определяется
необходимая версия виртуального метода Show.
Крайним случаем массива полиморфных указателей является массив, в
который можно собрать указатели на объекты абсолютно любых классов.
Для этого достаточно объявить массив с типом Object/TObject. На практике
применение таких ―сверхуниверсальных‖ массивов должно быть
оправданным и обоснованным.
Другим важным применением полиморфных объектных переменных
является использование их в качестве параметров методов. Например,
объявим метод с формальным параметром классового типа фигур:
procedure SomeMet (aFigs : TFigure);
void SomeMet (Figure aFigs);
В качестве фактического значения при вызове этого метода можно
передавать указатель на объект любого дочернего графического класса:
SomeMet (Circle); // выполнение кода метода для окружности
SomeMet (Rect); // выполнение кода метода для прямоугольника
объект-
окружность
объект-
эллипс
объект-
отрезок
объект-
прямоугольник
объект-
отрезок
адрес
объекта 1
адрес
объекта 2
адрес
объекта 3
адрес
объекта 4
адрес
объекта 5
Аналогично ''сверхуниверсальном'' массиву можно объявить метод со
―сверхуниверсальным‖ параметром:
procedure SuperMet (aObj : TObject);
void SuperMet (Object aObj);
Такой метод может принимать в качестве фактического значения ссылку на
объект любого класса!
К сожалению, при использовании полиморфных объектных переменных
часто возникает ряд проблем. Поскольку полиморфный указатель может
динамически менять свой тип, возникает необходимость ответа на вопрос:
Объект какого класса адресует полиморфная объектная
переменная в данный момент работы приложения?
Например, для массива указателей на графические фигуры при
использовании конструкций типа Figs[i] возникает неопределенность:
какой графический объект адресуется этим элементом?
Для ответа на подобные вопросы используются специальные
механизмы, которые для каждого объекта в каждый момент времени
работы программы позволяют получать информацию о его текущем
классе. Эта информация оформляется в виде таблиц и хранится в
оперативной памяти в специальном внутреннем формате. Подобные
таблицы часто называют таблицами метаданных. Метаданные – это данные
о данных, т.е. данные, которые описывают структуру других данных, в
нашем случае – структуру классов.
Несмотря на то, что конкретный состав метаданных различен для
разных языков, можно указать наиболее часто используемые компоненты:
имя класса
указатель на таблицу метаданных родительского класса (тем
самым, переходя последовательно к родительским классам, можно
для любого класса восстановить всю цепочку наследования)
байтовый размер объекта
список всех полей данных с их атрибутами
список всех методов с их атрибутами (открытый, защищенный,
закрытый, абстрактный, виртуальный и т.д.)
Для доступа к метаданным можно использовать специальные методы, а
сам процесс получения этой информации во время работы программы
обозначают термином reflection (рефлексия, отражение).
Все объекты одного и того же класса связаны со своей таблицей
метаданных, для чего в состав каждого объекта включается скрытое
адресное поле.
Например, для массива указателей на графические фигуры связь
соответствующих структур (сам массив, адресуемые объекты и
соответствующие таблицы метаданных) можно схематично представить
следующим образом:
Из этой схемы видно, что ни элемент массива указателей, ни сам объект
не определяют классовый тип данных, этот тип определяется таблицей
адрес
объекта 1
адрес
объекта 2
адрес
объекта 3
адрес
объекта 4
адрес
объекта 5
Массив
полиморфных
указателей
50, 50,
100
100, 200,
200, 50
50, 50,
200, 100
50, 50,
200, 100
100, 200,
50, 0, 90
метаданные
для класса
окружностей
метаданные
для класса
эллипсов
метаданные для
класса
прямоугольников
метаданные
для класса дуг
метаданные
для класса
фигур