СТАТЬЯ 03.08.01

Предыдущая часть

Профессиональная разработка приложений с помощью Delphi5

Часть 1. Основы объектно-ориентированного программирования

Сергей Трепалин,
УКЦ Interface Ltd.
КомпьютерПресс #1 2001

Статья была опубликована в КомпьютерПресс

Переписанные методы. Полиморфизм. Абстрактные методы

Методы классов могут вызываться как непосредственно программистом, так и автоматически в ответ на какие-либо события. Предположим, перед вызовом метода необходимо выполнить какое-либо действие. Если метод вызывается в явном виде из кода программы, то программист может перед вызовом метода дописать необходимый код. Однако достаточно часто бывает необходимо выполнить действия перед автоматическим вызовом классом какого-либо метода в ответ на какое-либо событие (или после вызова метода). Например, класс TEdit (однострочный редактор текста) позволяет редактировать текст. При этом, например, программист хочет, чтобы пользователь не мог покинуть данный элемент управления, пока в нем не будет введено корректное значение какого-либо текста (например, целое число). Но покинуть данный элемент управления и перейти к другому пользователь может несколькими способами – щелкнуть мышкой на другом элементе, нажать акселератор или клавишу Tab. Если бы программист никак не мог изменить имеющиеся методы, он вынужден был бы перехватывать события о нажатой клавише мыши на всех имеющихся на форме элементах управления, проверять содержимое целевого элемента и при необходимости возвращать ему фокус ввода. Такие же проверки необходимо было бы сделать для обработчика событий, связанных с нажатием на клавиши…

К счастью, в классах виртуальные и динамические (но не статические!) методы можно подменить на другие, созданные программистом. При этом, если данный метод вызывается автоматически, будет выполняться уже новый метод, написанный программистом. Такая подмена осуществляется при использовании служебного слова override. В данном случае (проверка содержимого редактора перед выходом из него) решение будет заключаться в следующем. Любой объект класса TWinControl (и TEdit) вызывает метод DoExit перед тем, как он теряет фокус ввода. Этот метод является динамическим, и его можно переписать:

TMyEdit=class(TEdit)
 protected
 procedure DoExit; override;
 end;
…
implementation
…
procedure TMyEdit.DoExit;
var
 N,I:integer;
begin
 inherited DoExit;
 Val(Text,N,I);
 if I<>0 then begin
 MessageDlg(‘Illegal value’,mtError,[mbOK],0);
 SetFocus;
 end;
end;

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

procedure TForm1.FormCreate(Sender: TObject);
begin
 with TMyEdit.Create(Self) do begin
 Parent:=Self;
 Left:=200;
 Top:=100;
 end;
end;

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

Таким образом, переписывание виртуального или динамического метода осуществляется следующим образом. В классе-потомке определяется метод с тем же самым названием и с тем же самым списком параметров, который был ранее объявлен в каком-либо из классов-предков данного класса. При этом не имеет значения, как называются формальные параметры переписываемого метода, значение имеет их порядок следования, модификаторы (var, const) и их тип. Например, на уровне TWinControl определен виртуальный метод: procedure AlignControls(AControl: TControl; var Rect: TRect); При его переписывании в каком-либо классе-потомке данный метод можно определить следующим образом:

procedure AlignControls(AC: TControl; var R: TRect); override;

Однако компилятор Delphi не пропустит перечисленные ниже определения:

procedure AControls(AControl: TControl; var Rect: TRect); override; {Name 
    is not consistent}
procedure AlignControls(AControl: TControl; Rect: TRect); override; {var 
    missed}
procedure AlignControls(var Rect: TRect); override; {Parameter list differs}

Следует обратить внимание на использованное в реализации метода DoExit служебное слово inherited. Подробно его значение мы обсудим позднее, пока же будем считать, что он вызывает метод класса-предка. При переписывании методов в большинстве случаев необходимо вызывать методы класса-предка или, по крайней мере, четко представлять себе, что случится, если не вызвать метод предка. Забывание оператора inherited, как правило, приводит к фатальным сбоям в приложении, причем часто это проявляется не сразу.

Итак, рассмотрим еще раз, что происходит в классе TEdit и его потомке TMyEdit. Класс TEdit устроен таким образом, что перед тем, как он теряет фокус ввода, приложение обращается к таблице динамических методов TEdit и извлекает оттуда адрес 27-го метода (метод DoExit среди динамических определен 27-м по счету). После этого управление процессом передается по найденному адресу. Класс-потомок TMyEdit имеет собственные таблицы виртуальных и динамических методов. Таблица динамических методов отличается тем, что в нем 27-й адрес уже указывает на реализованный нами метод DoExit. Соответственно приложением извлекается уже новый адрес, и управление процессом передается вновь реализованному методу. При его старте происходит обращение к таблице динамических методов класса TEdit и вызывается 27-й метод – это делает строка inherited DoExit. Затем проверяется содержимое свойства Text в экземпляре класса TMyEdit, и если это не целое число, то об этом сообщается пользователю и фокус ввода вновь переносится на редактор текста. Схематически это можно изобразить на рисунке следующим образом.

При забывании директивы override появляется новый, статический метод с тем же самым названием и списком параметров, что и ранее определенный виртуальный (или динамический) метод. При этом новый метод делает «невидимым» старый, то есть при явном вызове метода из кода приложения будет вызываться новый, определенный программистом метод. Если метод вызывается классом в ответ на какое-либо событие, по-прежнему будет вызываться старый метод (адрес в таблицах не изменится). Такое поведение объекта часто приводит к печальным последствиям, которые иногда очень трудно обнаруживаются.

С переписываемыми методами тесно связано и другое понятие для классов – полиморфизм, суть которого заключается в том, что родительский класс имеет какой-либо виртуальный или динамический метод, а в различных его потомках этот метод переписывается по-разному. После этого выполняя формально одни и те же методы, можно добиться принципиально разного результата . Хороший пример полиморфных классов – класс TStream (базовый) и три его потомка – TFileStream, TMemoryStream и TResourceStream. Эти классы используются для хранения и передачи данных в двоичном формате. В базовом классе TStream определено несколько абстрактных методов, например:

function Write(const Buffer; Count: Longint): Longint; virtual;

а в классах-потомках этот метод переписан таким образом, что записывает данные из переменной Buffer в файл (TFileStream), или в ОЗУ (TMemoryStream), или в ресурсы (TResourceStream). Программист при реализации приложения может определить метод для сохранения своих данных в двоичном формате:

procedure SaveToStream(Stream:TStream);
begin
 Stream.Write(FYear,sizeof(FYear));
 Stream.Write(FMonth,sizeof(FMonth));
 {... A very long code to save all data as binary may 
    be inserted here …}
end;

Далее, вызывая этот метод и используя разные классы-потомки, можно сохранить данные либо в памяти компьютера, либо в файле:

procedure TForm1.Button1Click(Sender: TObject);
var
 MStream:TMemoryStream;
 FStream:TFileStream;
begin
 MStream:=TMemoryStream.Create;
 SaveToStream(MStream); {Store data in memory}
 DoSomething(MStream); {Manipulation with memory data}
 MStream.Free;
 FStream:=TFileStream.Create('C:\Test.dat',fmCreate);
 SaveToStream(FStream); {Store data in file}
 FStream.Free;
end;

В классах, являющихся базовыми для полиморфизма, часто используют абстрактные методы. Они объявляются со служебным словом abstract:

TMyClass=class(TObject)
protected
 procedure DisplayGraphic(Canvas:TCanvas); virtual; 
    abstract;
end;

При объявлении метода абстрактным реализация этих методов не требуется (более того – не допускается компилятором). В секции implementation для метода DisplayGraphic абсолютно ничего писать не надо. Для абстрактного метода в виртуальной (или динамической) таблице методов резервируется запись, куда помещается адрес nil. В классах-потомках на место этого адреса подставляются реальные адреса.

При попытке вызвать абстрактный метод возникает исключение – метод-то отсутствует! В частности, для вышеприведенного примера метод Write класса TStream является абстрактным и при попытке вызвать метод SaveToStream

procedure TForm1.Button1Click(Sender: TObject);
var
 Stream:TStream;
begin
 Stream:=TStream.Create;
 SaveToStream(Stream); {Store data in ???}
 Stream.Free;
 end;

возникает ошибка:

Классы, содержащие абстрактные методы, называют абстрактными; они являются базовыми для создания классов-потомков. В любом случае не следует создавать экземпляры абстрактных классов в приложении! Компилятор Delphi тем не менее позволяет осуществлять вызов конструкторов абстрактных классов с соответствующим предупреждением.

Перегрузка методов

Так же как и для обычных методов, в методах класса допустима директива overload – перегружаемый метод:

TOver1Class=class(TObject)
public
 procedure DoSmth(N:integer); overload;
 class procedure DoSecond(N:integer); overload; dynamic;
end;

Перегружаемые методы могут быть как классовыми, так и обычными. Они способны быть статическими, динамическими и виртуальными. Следующие перегружаемые методы могут быть объявлены как в исходном классе, так и в классах-потомках:

TOver2Class=class(TOver1Class)
public
procedure DoSmth(S:string); overload;
class procedure DoSecond(S:string); reintroduce; overload; dynamic;
end;

Теперь можно вызывать методы DoSmth и DoSecond из экземпляра TOver2Class с целочисленным и строковым параметром:

procedure TForm1.Button3Click(Sender: TObject);
var
 CO:TOver2Class;
begin
 CO:=TOver2Class.Create;
 CO.DoSmth(1);
 CO.DoSmth('Test');
 CO.Free;
end;

В файлах помощи Delphi рекомендуют использовать директиву reintroduce, когда вводится перегружаемый виртуальный метод, но и без этой директивы приложение прекрасно работает. Директива reintroduce просто подавляет сообщение компилятора (warning) о возникновении скрытого метода.

Продолжение статьи

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 03.08.01