Наследование статических методов
Все показанные до сих пор методы, связанные с типами объек- тов TEmployee, THourly, TSalaried и TCommissioned, являются ста- тическими методами. Однако, со статическими методами связана пpоблема наследования.
Для того, чтобы разобраться с этой проблемой, отложим в сто- рону наш пример с платежной ведомостью и рассмотрим другой упро- щенный и нереалистичный, но показательный пример. Вернемся к кры- латым насекомым. Предположим, что нужно создать программу, кото- рая будет рисовать на экране различные типы летающих насекомых. Предположим, вы решили, что на вершине иерархии будет находиться объект Winged. Пусть вы планируете, что новые типы объектов лета- ющих насекомых как будут строиться как потомки Winged. Например, вы можете создать тип объекта Bee, который отличается от родс- твенных крылатых насекомых тем, что имеет жало и полосы. Конечно, у пчелы есть другие отличающие ее характеристики, но в нашем при- мере это может выглядеть следующим образом:
type TWinged = object(Insect) procedure Init(AX, AY: Integer) { инициализирует экземпляр } рrocedure Show; { отображает крылатое насекомое на экране } рrocedure Hide; { стирает крылатое насекомое с экрана } рrocedure MoveTo(NewX, NewY : Integer); { перемещает крылатое насекомое } end;
tyрe TBee = object(Winged) . . . рrocedure Init(AX, AY: Integer) { инициализирует экземпляр Bee } рrocedure Show; { отображает пчелу на экране } рrocedure Hide; { стирает пчелу с экрана } рrocedure MoveTo(NewX, NewY : Integer); { перемещает пчелу } end;
И TWinged, и TBee имеют по четыре метода. TWinged.Init и TBee.Init инициализируют экземпляр соответствующих объектов. Ме- тод TWinged.Show знает, как рисовать крылатое насекомое на экране, а метод TBee.Show - как рисовать пчелу (крылатое насеко- мое с полосками на теле и с жалом). Метод TWinged.Hide знает, как стирать крылатое насекомое с экрана, а метод TBee.Hide - как стирать пчелу. Два метода Show отличаются друг от друга, равно как и два метода Hide.
Однако, методы TWinged.MoveTo и TBee.MoveTo полностью одина- ковы. В нашем примере X и Y определяют положение на экране.
рrocedure TWinged.MoveTo(NewX, NewY: Integer); begin Hide; X := NewX; {новая координата X на экране} Y := NewY; {новая координата Y на экране} Show; end;
рrocedure TBee.MoveTo(NewX, NewY: Integer); begin Hide; X := NewX; {новая координата X на экране} Y := NewY; {новая координата Y на экране} Show; end;
Не изменилось ничего, кроме копирования программы и поста- новки квалификатора TBee перед идентификатором MoveTo. Так как методы одинаковы, зачем нужно помещать MoveTo в TBee? Ведь Bee автоматически наследует MoveTo от TWinged. Поэтому не нужно переопределять метод MoveTo из TWinged, но это именно то место, где возникает проблема в случае статических методов.
Термин "статический" был выбран для описания методов, не яв- ляющихся виртуальными - термин, который мы введем далее. Факти- чески, виртуальные методы являются решением этой проблемы, но прежде чем понять решение, вам следует разобраться в самой проб- леме.
Признаки проблемы состоят в следующем: пока копия метода MoveTo не будет помещена в область действия TBee для подавления метода MoveTo объекта TWinged, метод не будет работать правильно, если он будет вызываться из объекта типа TBee. Если TBee запуска- ет метод MoveTo объекта TWinged, так то, что движется по экрану, является крылатым насекомым, а не пчелой. Только когда TBee вызы- вает копию метода MoveTo, определенного в его собственной области действия, на экране с помощью вызовов Show и Hide будут рисовать- ся и стираться пчелы.
Почему это так? Это объясняется способом, которым компилятор разрешает вызовы методов. Когда компилируются методы Bee, то сна- чала встречаются TWinged.Show и TWinged.Hide и их код компилиру- ется в сегмент кода. Немного позднее в файле встречается метод Winged.MoveTo, который вызывает TWinged.Show и TWinged.Hide. Как и при вызове любой процедуры, компилятор замещает ссылки на TWinged.Show и TWinged.Hide в исходном коде на их адреса, сгене- рированные в сегменте кода. Таким образом, когда вызывается код TWinged.MoveTo, он, в свою очередь, вызывает TWinged.Show и TWinged.Hide со всеми вытекающими последствиями.
До сих пор это был типичный для Borland Pascal сценарий и он был бы справедлив (за исключением номенклатуры), начиная с версии 1.0 Turbo Pascal 1983 года. Однако, дело меняется, когда вы вклю- чаете в этот сценарий принцип наследования. Когда TBee наследует метод от TWinged, он (TBee) использует метод в точности так, как тот был откомпилирован.
Снова посмотрите, что должен наследовать TBee, если он нас- ледует TWinged.MoveTo:
рrocedure TWinged.MoveTo(NewX, NewY: integer); begin Hide; { Вызов Winged.Hide } X := NewX; Y := NewY; Show { Вызов Winged.Show } end;
Комментарии здесь приведены для того, чтобы подчеркнуть тот факт, что если Bee вызывает метод TWinged.MoveTo, то он также вы- зывает TWinged.Show и TWinged.Hide, а не TBee.Show и TBee.Hide. Поскольку TWinged.MoveTo вызывает методы TWinged.Show и TWinged.Hide, TWinged.MoveTo нельзя наследовать. Вместо этого, он должен быть переопределен своей второй копией, которая вызывает копии Show и Hide, определенные внутри области действия второй копии, то есть, TBee.Show и TBee.Hide.
При разрешении вызовов методов, логика компилятора работает так: при вызове метода компилятор сначала ищет метод, имя которо- го определено внутри типа объекта. Тип TBee определяет методы с именами Init, Hide, Show и MoveTo. Если метод TBee должен был вызвать один из этих четырех методов, то компилятор заменил бы вызов на адрес одного из собственных методов Bee.
Если в типе объекта не определен метод с таким именем, то компилятор поднимается выше к непосредственному родительскому ти- пу в поисках метода с указанным именем. Если метод с таким именем найден, то адрес родительского метода замещает имя в исходном ко- де дочернего метода. Если метод с таким именем не найден, то ком- пилятор продолжает продвигаться вверх по родительским объектам в поисках метода. Если компилятор наталкивается на самый первый (высший) тип объекта, то он выдает сообщение об ошибке, указываю- щее, что ни одного такого метода не определено.
Однако, если статический наследуемый метод найден и исполь- зуется, то вы должны помнить, что вызываемый метод является в точности таким, как он определен и компилирован для родительского типа. Если родительский метод вызывает другие методы, то вызывае- мые методы будут также родительскими методами, даже если дочерний объект содержит методы, которые переопределяют родительские.