Delphi имеет необычно богатую языковую поддержку полиморфного поведения. Самая простая и базовая, которую большинство программистов ассоциируют с полиморфизмом, - это виртуальные методы.
Виртуальный метод объявляется в своём базовом классе с использованием директивы
virtual
:
TShape = class procedure Draw(Canvas: TCanvas); virtual; end;Базовый класс может иметь реализацию по умолчанию для виртуального метода, а может и не иметь. Если реализации по умолчанию нет, то вы помечаете метод как абстрактный (директива
abstract
), вынуждая наследников класса создавать свою реализацию метода в обязательном порядке.
TRectangle = class(TShape) procedure Draw(Canvas: TCanvas); override; end;Всё это - базовые вещи, которые знают все Delphi программисты. В зависимости от реализации (и документации!) базового класса, класс-наследник может решить вызывать унаследованный метод в самом начале, перед выполнением своих действий, либо в середине (довольно редко), либо после своих действий, в конце, либо же не вызывать вовсе. Существует два способа вызова унаследованного варианта метода, с тонкими отличиями:
procedure TRectangle.Draw(Canvas: TCanvas); begin inherited Draw(Canvas); Canvas.Rectangle(FRect); end;Этот код безусловно вызовет унаследованный метод
Draw
базового класса. Если метод в базовом классе - абстрактный, то этот вызов завершиться неудачей, возбуждая исключение EAbstractError
во время выполнения (или Run-Time ошибку 210, если вы не используете исключения).Альтернативный синтаксис вызова - просто написать
inherited;
, например:
procedure TRectangle.Draw(Canvas: TCanvas); begin inherited; Canvas.Rectangle(FRect); end;Этот код будет работать идентично предыдущему для случаев, когда базовый класс содержит не абстрактный метод. Этот код также автоматически создаётся средством автодополнения кода, когда вы реализуете замещение метода (override). Кроме того, он же используется IDE, при вставке обработчиков событий форм с визуальным наследованием.
Если же метод базового класса является абстрактным, либо же базовый класс вообще не содержит метода (для не виртуальных методов), то вызов
inherited
становится noop
(No-Operation - пустым оператором). Компилятор не генерирует для него кода (и поэтому вы не можете поставить на него точку останова). Этот механизм является частью отличной версионной устойчивости языка Delphi.Один подводный камень с синтаксисом
inherited;
- он не поддерживается для функций. Для функций вам нужно использовать явный синтаксис с указанием имени метода и его аргументов. К примеру:
type TMyClass = class function MethodC: integer; virtual; end; TMyDescendent = class(TMyClass) function MethodC: integer; override; end; function TMyClass.MethodC: integer; begin Result := 43; end; function TMyDescendent.MethodC: integer; begin // inherited; // ошибка // Result := inherited; // ошибка Result := inherited MethodC; // Ok end;Это может выглядеть как чрезмерное ограничение дизайна языка Delphi, но я думаю, что это не случайно. Смысл этого, вероятно, в том, что если
TMyClass.MethodC
является абстрактным (или будет сделан абстрактным в будущем), то присваивание результата вызова в Result
в классе-потомке будет удалено, что приведёт к неожиданному неопределенному значению. Это, конечно же, приведёт к скрытым багам в коде.Однако, я думаю, что здесь есть небольшой пробел в синтаксисе унаследованного вызова. Во многих отношениях процедура, которая принимает
out
параметры (а в некоторых случаях и var
параметры), ведёт себя как функция, возвращающая результат. Так, на мой взгляд, синтаксис inherited;
должен быть запрещён при вызове методов с out
(и, возможно, var
) параметрами. Сейчас это не так.
procedure TMyDescendent.MethodB(out A: integer); begin inherited; Writeln(A); end;Этот код означает, что если метод родительского класса является абстрактным (или просто отсутствует в случае не виртуального метода), то значение выходного параметра будет неопределённым. На мой взгляд, компилятор должен запрещать такие вызовы
inherited;
, требуя явного синтаксиса inherited MethodB(A);
Но кота уже выпустили из мешка и уже слишком поздно что-то менять - блокировка подобных вызовов с ошибкой компиляции определённо поломает кучу кода, так что, вероятно, это тянет максимум на предупреждение (warning).
Вообще не знал, как использовать директиву virtual *oops*
ОтветитьУдалитьСпасибо за понятную статью! :)
Один подводный камень с синтаксисом inherited; - он не поддерживается для функций. Для функций вам нужно использовать явный синтаксис с указанием имени метода и его аргументов
ОтветитьУдалитьЧестно - впервые об этом слышу. Пользовался таким синтаксисом и в Д7, и сейчас в ХЕ - никаких ограничений. И даже ваш пример отлично компилируется и работает как ожидается
Насколько я понимаю, это копия старой статьи из The Delphi Magazine (могу ошибаться, конечно). Но, в любом случае, оригинал был написан, видимо, довольно давно. Если не путаю, то это поведение было до Delphi 4 включительно, а в Delphi 5 его убрали.
ОтветитьУдалитьТекст решил оставить для ознакомления с проблемой абстрактного метода-функции. Хотя теперь понимаю, что надо было добавить примечание.
Один подводный камень с синтаксисом inherited; - он не поддерживается для функций.
ОтветитьУдалитьВ Delphi XE именно так. Приходится писать влоб:
Result := inherited;
Иначе значение из базового класса не передается.