Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по информатике все.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
986.62 Кб
Скачать

Виртуальные методы

Рассмотрим иерархическую структуру объектов задачи передвижения изображений, представленную на Рис.12.

Рис.12. Структура объектов

Для этой структуры был описан родительский объект TImage, задающий абстрактный элемент, и объект-потомок TSymbol, соответствующий символу. Напомним, что в объектном типе TImage определены координаты левого верхнего угла изображения (X,Y), размеры изображения (RX,RY), признак видимости изображения (V), а также следующие методы:

- Init - инициализирует координаты X,Y; - On, Off - включает/выключает изображение; - Move - передвигает изображение на экране; - GetX, GetY - определяет координаты X,Y; - PrintImage - выводит изображение на экран,

Дополним нашу задачу описанием другого изображения - строки:

type

PStroka = ^TStroka;

TStroka = object(TImage)

Str: string; {обрабатываемая строка}

procedure Init(A,B: integer; St:string);

procedure On;

procedure Off;

procedure PrintImage;

end;

procedure TStroka.Init(A,B:integer; St:string);

begin

TImage.Init(A,B);

Str := St;

end;

procedure TStroka.On;

begin

V := TRUE;

GotoXY(X,Y);

write(Str);

end;

procedure TStroka.Off;

var

Str1:string;

begin

V := FALSE;

GotoXY(X,Y);

FillChar(Str1,Length(Str)+1,' ');

{процедура заполняет побайтно переменную символом пробела, начиная с нулевого байта}

Str1[0]:=Chr(Length(Str)); {занесение длины строки в нулевой байт}

write(Str1);

end;

procedure TStroka.PrintImage;

begin

writeln('Строка: ',Str);

end;

Методы GetX, GetY, Move являются общими для обоих потомков (символа и строки) и не требуют переопределения. Методы Init, On, Off, PrintImage для каждого вида изображений являются индивидуальными и должны быть переопределены заново для каждого объекта. Поскольку объект TImage является абстракцией, тела этих методов в его определении являются пустыми. Теперь нам необходимо обеспечить настройку метода Move на тип объекта: символ или строку. Самое простое решение - традиционное: перед каждым вызовом Move производить анализ экземпляра объекта и в зависимости от результата подключать нужную процедуру On и Off. Мало того, что эта операция достаточно трудоемка, она абсолютно не соответствует концепциям ООП.

Рассмотрим более изящное решение, поддерживаемое объектно-ориентированными средствами. При обращении к методу Move экземпляр потомка вызывает унаследованный от родителя метод Move, который жестко связан с методами TImage.On, TImage.Off, т.к. они были вместе откомпилированы. Связь этих методов - статическая, поскольку она была установлена при компиляции. Чтобы разорвать статическую связь метода Move c методами TImage.On, TImage.Off, которые переопределяются для каждого объекта, объявим эти методы виртуальными. Таким образом существует возможность изменять тот или иной метод объекта в процессе выполнения программы, устанавливая так называемую динамическую связь методов. Для того, чтобы определить виртуальный метод, необходимо указать после его заголовка в родительском типе служебное слово Virtual. Переопределенные одноименные методы всех потомков должны быть также отмечены как виртуальные. Кроме того, все виртуальные методы должны иметь одинаковый набор формальных параметров. В процессе выполнения программы обеспечивается вызов виртуального метода, соответствующего вызываемому объекту, и один и тот же метод будет работать по-разному, например, метод TImage.PrintImage выводит либо символ, либо строку в зависимости от типа объекта, для которого он вызывается. В этом заключается смысл полиморфизма, когда при вызове одного и того же метода каждый объект может "заказывать" особенности реализации этого метода. Благодаря полиморфизму, удается существенно повысить эффективность разработки больших программных систем. Естественно ожидать, что выполнение программы с виртуальными методами будет происходит несколько медленнее, кроме того требуется дополнительный расход оперативной памяти.

Использование виртуальных методов накладывает дополнительное требование на объектный тип. В этом случае объект должен содержать хотя бы один метод-конструктор, определяющий процедуру создания данного объекта. Синтаксически конструктор объявляется как обычная процедура с заменой ключевого слова Procedure на зарезервированное слово Constructor. Смысл введения конструктора заключается в том, что он дополнительно формирует в экземпляре объекта необходимую информацию для последующих вызовов виртуальных методов. Конструктор должен быть применен к экземпляру объекта до первого вызова виртуального метода. Обычно в качестве конструктора выбирают метод, инициализирующий значения полей экземпляра объекта. Сам конструктор не может быть виртуальным. В нашем примере в качестве конструктора выбрана процедура Init. Теперь объектные типы должны определяться следующим образом:

type

PImage=^TImage;

TImage=object

. . .

constructor Init(A,B:integer);

procedure On; virtual;

procedure Off; virtual;

procedure Move(Dx,Dy:integer);

procedure PrintImage; virtual;

. . .

end;

PSymbol=^TSymbol;

TSymbol=object(TImage)

. . .

constructor Init(A,B:integer; C:char);

procedure On; virtual;

procedure Off; virtual;

procedure PrintImage; virtual;

end;

Мы уже отмечали, что экземпляры объектов так же, как и переменные любых других типов, можно размещать в динамической памяти с помощью процедуры New. Именно для такого размещения были определены указатели в определении объектных типов PImage, PSymbol и PStroka, например:

var

PS:PSymbol;

. . .

New(PS); {выделение памяти для экземпляра объекта}

PS^.Init(1,1,Ch);

Dispose(PS); {освобождение памяти}

В последних версиях Турбо-Паскаля допускается расширенный синтаксис процедур New и Dispose:

New(PS, Init(1,1,Ch));

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

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

Destructor TImage.Done;

begin         end;

Теперь можно использовать расширенный вызов процедуры Dispose с деструктором:

Dispose(PS, Done);

В результате выполнятся действия, описанные в деструкторе Done (если они есть), а затем процедура Dispose освободит количество байтов памяти, соответствующее типу экземпляра объекта PS. Вызов деструктора вне процедуры Dispose естественно не приведет к освобождению памяти, занимаемой экземпляром объекта