«Ни один ремесленник, который стремится к вершинам своей профессии, не примет негодных инструментов; и ни один производитель, который ценит качество работы, не будет упрашивать ремесленника принять их»
25. Связывание методов
(Английский термин – Method binding.)
Методы могут быть статическими (static – по умолчанию), виртуальными (virtual) или динамическими (dynamic). Виртуальные и динамические методы могут быть перекрытыми (override) и абстрактными (abstract). Эти спецификаторы (designators) методов играют роль тогда, когда объектом является класс-наследник. Они определяют, какая именно реализация метода будет вызвана в том или ином случае.
Статические методы (Static methods).
Методы являются статическими по умолчанию и слово static не указывается. Когда вызывается статический метод, для определения конкретной реализации метода используется информация времени компиляции. В следующих описаниях методы Draw являются статическими:
type
TFigure = class
procedure Draw;
end;
TRectangle = class(TFigure)
procedure Draw;
end;
С учетом этих описаний следующий код иллюстрирует вызов статических методов:
var
Figure: TFigure;
Rectangle: TRectangle;
begin
Figure := TFigure.Create;
Figure.Draw; // вызывается TFigure.Draw
Figure.Free;
Figure := TRectangle.Create;
Figure.Draw; // вызывается TFigure.Draw
TRectangle(Figure).Draw; // вызывается TRectangle.Draw
Figure. Free;
Rectangle := TRectangle.Create;
Rectangle.Draw; // вызывается TRectangle.Draw
Rectangle.Free;
end;
Виртуальные и динамические методы.
Для того чтобы сделать метод виртуальным или динамическим, надо добавить соответствующую директиву в его описание. Эти методы, в отличие от статических, могут быть перекрытыми (override) в классах наследниках.
Когда вызывается перекрытый метод, действительный, т.е. времени выполнения, тип класса или объекта, использованный в вызове метода (а не объявленный тип), определяет, какая реализация будет вызвана.
Для перекрытия метода его надо объявить с директивой override. Заголовок перекрывающего метода должен в точности соответствовать заголовку метода в родительском классе. В программе Method_Binding иллюстрируется специфика виртуальных и перекрытых методов.
Program Method_Binding;
{Иллюстрация связывания виртуальных и перекрытых методов}
{$APPTYPE CONSOLE} // консольное приложение
uses SysUtils;
type
TFigure = class
Procedure DrawV; virtual;
Procedure DrawO; virtual; {в производном классе этот метод будет объявлен
как перекрытый, но в родительском его надо объявлять просто виртуальным}
end;
TRectangle = class(TFigure)
Procedure DrawV; virtual; {[Warning] :
Method 'DrawV' hides virtual method of base type 'TFigure'
Это предупреждение можно убрать с помощью директивы reintroduce,
которая указывается вместо virtual}
Procedure DrawO; override;
end;
TEllipse = class(TRectangle)
Procedure DrawV; virtual;
Procedure DrawO; override;
end;
{ ----------------реализация TFigure----------------- }
procedure TFigure.DrawO;
begin
WriteLn('TFigure.DrawO');
end;
procedure TFigure.DrawV;
begin
WriteLn('TFigure.DrawV');
end;
{ ----------------реализация TRectangle ----------------}
procedure TRectangle.DrawO;
begin
WriteLn('TRectangle.DrawO');
end;
procedure TRectangle.DrawV;
begin
WriteLn('TRectangle.DrawV');
end;
{ ----------------реализация TEllipse ----------------}
procedure TEllipse.DrawO;
begin
WriteLn('TEllipse.DrawO');
end;
procedure TEllipse.DrawV;
begin
WriteLn('TEllipse.DrawV');
end;
{ -----------------------------------------------------------}
var
F : TFigure;
T : TRectangle;
BEGIN
F:=TRectangle.Create;
F.DrawV; //вызываетсяTFigure.DrawV
F.DrawO; //вызываетсяTRectangle.DrawO
F.Free;
F:=TEllipse.Create;
F.DrawV; // вызываетсяTFigure.DrawV
F.DrawO; // вызываетсяTEllipse.DrawO
TRectangle(F).DrawV; // вызываетсяTRectangle.DrawV
TRectangle(F).DrawO; // вызываетсяTEllipse.DrawO
F.Free;
T:=TRectangle.Create;
T.DrawV; // вызываетсяTRectangle.DrawV
T.Free;
ReadLn;
END.
Несмотря на то, что только виртуальные и динамические методы могут перекрываться, все методы могут быть перегружены (overload).
Виртуальность или динамичность? (Virtual versus dynamic).
Виртуальные и динамические методы являются семантически эквивалентными. Различие между этими методами проявляется только на этапе выполнения в способе реализации собственно вызова метода. Виртуальные методы оптимизированы по скорости вызова, в то время как динамические – по объему кода.
В основном, виртуальные методы являются наиболее эффективным путем достижения преимуществ полиморфизма. Динамические методы полезны тогда, когда базовый класс имеет много потенциально перекрываемых методов, которые наследуются многочисленными производными классами приложения, однако только изредка в действительности перекрываются.
Перекрытие или прятание? (Overriding versus hiding)
Если объявление какого-либо метода имеет такой же заголовок, как и объявление наследуемого метода, и не имеет директивы override, новое объявление попросту "прячет" унаследованный метод без его перекрытия. Оба метода существуют в производном классе. Например:
type
T1 = class(TObject)
procedure Act; virtual;
end;
T2 = class(T1)
procedure Act; // Act переобъявлена, но не перекрыта
end;
var
SomeObject: T1;
begin
SomeObject := T2.Create;
SomeObject.Act; // вызывается T1.Act
T2(SomeObject).Act; // вызывается T2.Act
end;
Директива reintroduce.
Эта директива просто подавляет предупреждения компилятора о прятании прежде объявленного виртуального метода. Например
procedure DoSomething; reintroduce; { родительский класс также имеет метод DoSomething }
Абстрактные методы (Abstract methods).
Абстрактный метод – это виртуальный или динамический метод, который не имеет определяющего описания в том классе, где он объявлен. Его реализация откладывается до производного класса. Абстрактные методы должны объявляться с директивой abstract после virtual или dynamic. Например,
procedure DoSomething; virtual; abstract;
Абстрактный метод может быть вызван только в классе или объекте, в котором метод перекрыт, т.е. реализован.
Перегрузка методов (Overloading methods).
Метод может быть повторно объявлен с директивой overload. В этом случае, если повторно объявленный метод имеет отличающийся от родительского метода (в родительском классе) список параметров, он перегружает унаследованный метод без его прятания. Вызов такого метода в производном классе активирует любой метод, реализация которого имеет соответствующий список параметров.
Непонятно, почему в Help Delphi6 советуют использовать директиву reintroduce в объявлении перегруженного метода в производном классе. Пример:
type
T1 = class(TObject)
procedure Test(I: Integer); overload; virtual;
end;
T2 = class(T1)
procedure Test(S: string); overload; virtual;
end;
…
SomeObject := T2.Create;
SomeObject.Test('Hello!'); // вызов T2.Test
SomeObject.Test(7); // вызов T1.Test
Функциональность классов, между прочим, никак не изменится, если убрать директиву virtual.
«24. Поля и методы классов»
26. Конструкторы и деструкторы