Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Полиморфные объектные указатели



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

В объектном программировании правило строгого соответствия типов немного ослаблено. Это, в первую очередь, относится к объектным указателям. При определенных условиях объектные указатели могут адресовать не только объекты своего «родного» класса, но и объекты некоторых других классов. «Родным» (статическим) классом считается тот класс, который использован при объявлении объектной переменной.

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

Например, объявим для иерархии графических фигур следующие объектные переменные:

Fig: TFigure; Circ: TCircle; Ellipse: TEllipse; Rect: TRectangle;

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

Fig:= Circ; // указатель класса фигур адресует окружность

Fig:= Ellipse; // указатель класса фигур адресует эллипс

Fig:= Rect; // указатель класса фигур адресует прямоугольник

Circ:= Ellipse; // указатель класса окружностей адресует эллипс

Однако недопустимыми будут следующие присваивания:

Circ:= Fig; Rect:= Fig; Circ:= Rect; Ellipse:= Circ;

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

А если объявить переменную базового класса-родоначальника Object/TObject? Интересно, что такая переменная может адресовать объекты абсолютно любых классов, как стандартных, так и нестандартных. Во многом именно по этой причине компилятор при обработке объектной программы автоматически включает все нестандартные классы в общую иерархию. Это дает целый ряд интересных возможностей, рассматриваемых ниже.

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

var Figures: array [1..N] of TFigure;

for i:= 1 to N do Figures[i].Show; // показать все объекты

for i:= 1 to N do Figures[i].MoveTo (dx, dy); // переместить всех

Адрес объекта 1 Адрес объекта 2 Адрес объекта 3 Адрес объекта 4 Адрес объекта 5
Массив полиморфных указателей
Интересно, что в этих примерах используются оба проявления принципа полиморфизма – как полиморфные указатели, так и виртуальные переопределяемые методы.

                   
 
Объект окружность
 
Объект отрезок
 
Объект отрезок
 
Объект прямоугольник
 
Объект отрезок


Крайним случаем массива полиморфных указателей является массив, в который можно собрать указатели на объекты абсолютно любых классов. Для этого достаточно объявить массив с типом Object/TObject. На практике применение таких “сверхуниверсальных” массивов должно быть оправданным и обоснованным.

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

procedure SomeProc (aFigs: TFigures);

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

SomeProc (Circle); // выполнение кода процедуры для окружности

SomeProc (Rect); // выполнение кода процедуры для прямоугольника

Аналогично ‘сверхуниверсальному” массиву можно объявить подпрограмму со “сверхуниверсальным” параметром:

procedure SomeProc (aObject: TObject);

Такая подпрограмма может принимать параметр-указатель на объект любого класса!

При использовании полиморфных указателей в объектных программах возникает ряд проблем. Например, поскольку полиморфный указатель может динамически менять свой тип, довольно часто возникает необходимость ответа на вопрос: «Объект какого типа адресует полиморфный указатель в данный момент работы приложения?». Для этого при выполнении приложения должна быть доступна некоторая информация о каждом используемом в программе классе.

Набор сведений о классе, доступный при выполнении приложения, принято называть метаданными или информацией о типах времени выполнения (RunTime Type Information, RTTI). Эта информация создается компилятором, включается в исполняемый файл и доступна программному коду при выполнении приложения. Конкретный состав метаданных меняется от языка к языку, но наиболее часто используется следующая информация:

· имя класса;

· указатель на таблицу метаданных родительского класса (тем самым, переходя последовательно к родительским классам, можно для любого класса восстановить всю цепочку наследования);

· байтовый размер объекта.

Для доступа к этой информации в стандартных классах (например, в классе TObject) реализован ряд специальных методов.

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


Теперь можно вернуться к ответу на вопрос о проверке динамического типа полиморфных указателей. Для этих целей в объектных языках имеются специальные проверочные операторы. Например, в языке Delphi Pascal есть оператор is, который можно использовать в условном операторе следующим образом:

if (Figures[i] is TCircle) then

В данном примере оператор is возвращает истину, если полиморфный указатель Figures[i] адресует объекты-окружности или объекты дочерних для окружности классов. Если же Figures[i] адресует что-то другое (например, отрезки или прямоугольники), оператор is возвращает ложь.

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

for i:= 1 to N do

if (Figures[i] is TCircle) then Figures[i].MoveTo (параметры);

После ответа на вопрос о текущем динамическом типе полиморфного указателя логично задать следующий вопрос: «Как можно вызывать методы некоторого дочернего класса с помощью чужого указателя родительского типа?». Например, как с помощью указателя класса фигур Figures[i] вызвать методы дочернего класса окружностей?

Для этого прежде всего необходимо с помощью оператора is убедиться, что полиморфный указатель адресует объекты нужного нам класса, после чего выполнить операцию приведения типов, то есть явно привести родительский указатель к типу дочернего класса. Эта операция в разных языках реализована по-разному. Например, в Delphi Pascal используется специальный оператор приведения типов as. В итоге, получаем следующую конструкцию:

if (Figures[i] is TCircle) then (Figures[i] as TCircle).ChangeRad (…)

elseif (Figures[i] is TRect) then (Figures[i] as TRect).Rotate (…);

Здесь конструкция (Figures[i] as TCircle) выполняет явное приведение типа родительского указателя к классу TCircle, после чего с помощью этого указателя можно обращаться к методам класса окружностей обычным образом.

В языке Java для доступа к метаданным можно использовать специальный класс с именем Class. Информация о каждом используемом в программе классе сохраняется компилятором в файле с расширением.class. При выполнении программы, когда инициируется создание первого объекта некоторого класса, загружается соответствующий файл и автоматически создается объект класса Class для этого класса. При создании всех последующих объектов некоторого класса используется информация из уже существующего объекта класса Class. Таким образом, все объекты одного и того же класса ссылаются на свой единственный объект типа Class. Для доступа к этому объекту можно использовать встроенное поле с именем class, которым автоматически снабжаются объекты всех классов. Кроме того, можно использовать метод getClass (), который объявлен в базовом классе Object и наследуется всеми его потомками.

В классе Class реализовано несколько методов, наиболее полезными из которых являются:

· forName (String name) – возвращает указатель на объект класса Class для класса с именем name; метод является статическим (классовым) и поэтому может использоваться БЕЗ создания базового объекта;

· getName () – возвращает имя класса для вызывающего объекта;

· getSuperclass () – возвращает указатель на родителя вызывающего объекта;

· isInterface () – возвращает true, если соответствующая объектная переменная имеет тип интерфейса.

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

В завершение отметим, что в языке Java для проверки динамического типа объекта используется оператор instanceof, аналогичный оператору is языка Delphi Pascal. Например, для проверки типа объекта MyFig и приведения его к типу Circle можно записать следующий код:

if (MyFig instanceof Circle) (Circle(MyFig)).ChangeRad (…);

Здесь конструкция Circle(MyFig) и выполняет необходимое преобразование типа.





Дата публикования: 2014-11-02; Прочитано: 1412 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.008 с)...