- •Составление программ с использованиемобъектно-ориентированного подхода
- •Теоретические сведения
- •Инкапсуляция
- •Наследование
- •Полиморфизм
- •Виртуальные методы
- •Конструкторы
- •Присваивание объектов
- •Пример программирования с использованием объектных типов
- •Лабораторное задание
- •Порядок выполнения лабораторной работы
- •Требования к отчету
- •Контрольные вопросы
Виртуальные методы
Обратите внимание, что метод TCircle.Move (рис.9) слово в слово повторяет метод TPoints.Move (см. рис.2). Поэтому встаёт вопрос, надо ли переопределять метод Move в потомке TCircle? Разве недостаточно получить его по наследству от прародителя TPoints без переопределения?
Представим себе, что TCircle унаследовал у TPoints метод Move без переопределения. Тогда схема объектного типа TCircle будет выглядеть, как на рис.12.
+---------------------------------------------------------------+
¦ +----------------------------+ TCircle ¦
¦ ¦ TPoints ¦ ¦
¦ ¦ +---------------+ ¦ ¦
¦ ¦ ¦ X ¦ ¦ ¦
¦ ¦ +---------------¦ ¦ +---------------+ ¦
¦ ¦ ¦ Y ¦ ¦ ¦ Radius ¦ ¦
¦ ¦ +---------------¦ ¦ +---------------¦ ¦
¦ ¦ ¦ Visible ¦ ¦ ¦ GetR ¦ ¦
¦ ¦ +---------------¦ ¦ +---------------¦ ¦
¦ ¦ ¦ Init +<-------+------¦ Init ¦ ¦
¦ ¦ +---------------¦ ¦ +---------------¦ ¦
¦ ¦ ¦ GetX ¦ ¦ +-->¦ Show ¦ ¦
¦ ¦ +---------------¦ ¦ ¦ +---------------¦ ¦
¦ ¦ ¦ GetY ¦ ¦ +---¦ Hide ¦ ¦
¦ ¦ +---------------¦ ¦ +---------------+ ¦
¦ ¦ ¦ Show +<-----+ ¦ ¦
¦ ¦ +---------------¦ ¦ ¦ ¦
¦ ¦ ¦ Hide +<--+ ¦ ¦ ¦
¦ ¦ +---------------¦ ¦ ¦ ¦ ¦
¦ ¦ ¦ Move +------+ ¦ ¦
¦ ¦ +---------------+ ¦ ¦
¦ +----------------------------+ ¦
+---------------------------------------------------------------+
Рис.12
Посмотрим, как будет выполняться программа, приведённая на рис.13, если в описании объектного типа TCircle отсутствует переопределение метода Move.
При выполнении оператора P.Move(50,50) вызовется метод TPoints.Hide, и точка погаснет. Затем положение точки изменится, и вызовется метод TPoints.Show, в результате точка зажжётся в новом положении. Эта последовательность вызовов отмечена на схеме (рис.14) стрелками с буквой P.
При выполнении оператора C.Move(50,50) компилятор, не найдя метод Move среди собственных методов объектного типа TCircle, продолжит поиск метода в прародительском типе TPoints. Найденный метод TPoints.Move начнет использоваться, и как видно из схемы, вызовется метод TPoints.Hide, затем поля X,Y изменятся, затем вызовется метод TPoints.Show - все это приведет к перемещению по экрану точки. (Последовательность вызовов отмечена на схеме буквой С). Окружность при этом передвинута не будет, так как методы TCircle.Hide и TCircle.Show не вызываются методом TPoints.Move.
Жесткая связь метода TPoints.Move с методами TPoints.Hide и TPoints.Show была определена на этапе компиляции. Такой механизм называется статическим или ранним связыванием, а сами методы - статическими.
+---------------------------------------------------------------+
¦ Type ¦
¦ TPoints = Class ¦
¦ X,Y :Integer; ¦
¦ Visible:Boolean; ¦
¦ Procedure Init(A,B:Integer); ¦
¦ Function GetX:Integer; ¦
¦ Function GetY:Integer; ¦
¦ Procedure Show; ¦
¦ Procedure Hide; ¦
¦ Procedure Move(DX,DY:Integer) ¦
¦ End; ¦
¦ TCircle = Class (TPoints) ¦
¦ Radius:Word; ¦
¦ Procedure Init(A,B:Integer; R:Word); ¦
¦ Function GetR:Word; ¦
¦ Procedure Show; ¦
¦ Procedure Hide; ¦
¦ {Procedure Move наследуется без переопределения}¦
¦ End; ¦
¦ {......... Описание методов (Рис.2 и 9)........} ¦
¦ Procedure TPoints.Move(DX,DY:Integer); ¦
¦ Var ¦
¦ B:Boolean; ¦
¦ Begin ¦
¦ B:=Visible; ¦
¦ If B Then Hide; ¦
¦ X:=X+DX; ¦
¦ Y:=Y+DY; ¦
¦ If B Then Show ¦
¦ End; ¦
¦ ... ¦
¦ Var ¦
¦ P:TPoints; ¦
¦ C:TCircle; ¦
¦ ... ¦
¦ BEGIN ¦
¦ P:=TPoints.Create; {Создать экземпляр точки}¦
¦ C:=TCircle.Create; {Создать экземпляр окружности}¦
¦ ... ¦
¦ P.Init(50,50); ¦
¦ P.Show; ¦
¦ P.Move(50,50); ¦
¦ ... ¦
¦ C.Init(50,50,50); ¦
¦ C.Show; ¦
¦ C.Move(50,50); ¦
¦ ... ¦
¦ P.Destroy; {Уничтожить экземпляр точки}¦
¦ C.Destroy; {Уничтожить экземпляр окружности}¦
¦ ... ¦
+---------------------------------------------------------------+
Рис.13
+---------------------------------------------------------------+
¦ TCircle ¦
¦ +--------------------------------+ ¦
¦ ¦ TPoints ¦ ¦
¦ ¦ ... ¦ ... ¦
¦ ¦ +---------------+ ¦ +---------------+ ¦
¦ ¦ P+---->¦ Show +<--+C ¦ ¦ Show ¦<--+ ¦
¦ ¦ ¦ +---------------¦ ¦ ¦ +---------------¦ ¦ ¦
¦ ¦ P+---->¦ Hide +<--¦C ¦ ¦ Hide +---+ ¦
¦ ¦ ¦ +---------------¦ ¦ ¦ +---------------+ ¦
¦ ¦ +-----¦ Move +---+ ¦ ¦
¦ ¦ +---------------+ ¦ ¦
¦ ¦ ^ ^ ¦ ¦
¦ ¦ P¦ C+----------+----------+ ¦
¦ +------------+-------------------+ C¦ ¦
+----------------+------------------------------+---------------+
¦ ¦
P.Move(50,50) C.Move(50,50)
Рис.14
Итак, если мы не хотим переопределять в потомке TCircle метод Move, полученный по наследству от прародителя TPoints, то для правильного действия метода Move необходимо:
- разорвать статистическую связь метода Move с методами TPoints.Show и TPoints.Hide;
- обеспечить возможность для Move вызывать либо методы TPoints.Show и TPoints.Hide, либо методы TCircle.Show и TCircle.Hide в зависимости от того, экземпляр какого объектного типа вызывает метод Move. Т.е. связь метода Move c методами Show и Hide должна определяться на этапе исполнения.
Такой механизм называется динамическим или поздним связыванием и осуществляется, если вызываемые методы Show и Hide объявить виртуальными.
Для того чтобы объявить метод виртуальным, необходимо его заголовок в объявлении объектного типа дополнить стандартной директивой Virtual. Для объектных типов, содержащих виртуальные методы, должны выполняться следующие условия:
- если в прародительском типе метод объявлен виртуальным, то во всех потомках одноимённые методы также должны быть объявлены виртуальными с использованием директивы Override. Кроме того все они должны иметь одинаковый набор формальных параметров;
- если объектный тип содержит виртуальные методы, то он обязательно должен также содержать хотя бы один особый метод, называемый конструктором. Его назначение будет объяснено ниже.
С учётом этих замечаний можно объявить объектные типы TPoints и TCircle так, как показано на рис.15.
+--------------------------------------------------------------+
¦ Type ¦
¦ TPoints = Class ¦
¦ X,Y:Integer; ¦
¦ Visible: Boolean; ¦
¦ Constructor Init(A,B:Integer); ¦
¦ Function GetX:Integer; ¦
¦ Function GetY:Integer; ¦
¦ Procedure Show; Virtual; ¦
¦ Procedure Hide; Virtual; ¦
¦ Procedure Move; ¦
¦ End; ¦
¦ TCircle = Class(TPoints) ¦
¦ Radius:Word; ¦
¦ Constructor Init(A,B:Integer;R:Word); ¦
¦ Function GetR:Word; ¦
¦ Procedure Show; Override; ¦
¦ Procedure Hide; Override; ¦
¦ End; ¦
+--------------------------------------------------------------+
Рис.15
Заметим прежде всего, что теперь метод Move наследуется объектным типом TCircle без переопределения. Во-вторых, на этапе компиляции не установится жестких связей между методом Move и виртуальными методами Show и Hide.
Посмотрим, как теперь будет выполняться программа, приведённая на рис.13 с учётом изменений в описаниях типов TPoints и TCircle.
Теперь при выполнении оператора P.Move(50,50) вызовутся виртуальные методы Hide и Show, относящиеся к объектному типу TPoints, т.к. экземпляр Р, вызывающий метод Move, объявлен как экземпляр типа TPoints.
При выполнении оператора С.Move(50,50) вызовутся виртуальные методы Hide и Show, относящиеся к объектному типу TCircle, т.к. экземпляр C, вызывающий метод Move, объявлен как экземпляр типа TCircle.
Выбор нужных методов происходит на этапе исполнения программы. Цепочки вызовов процедуры Move экземплярами Р и С показаны на рис.16 стрелками.