Наследование и переопределение.

При построении нового объектного типа можно использовать некоторый ранее определенный тип. Пусть, например, необходимо построить объектный тип, управляющий простой геометрической фигурой - кругом - на экране дисплея. Структура информации для определения круга очень похожа на структуру для точки: здесь также необходимы поля Х и Y для фиксации центра круга и логическое поле Visible для определения видимости круга в текущий момент. Нужно еще добавить поле для хранения радиуса круга.

Традиционный стиль программирования допускает два решения. Во-первых, можно ввести для круга совершенно новую структуру, повторив в ней (может быть, под другими именами) те же поля X, Y и Visible и добавив новое поле Radius. Во-вторых, можно сконструировать структуру для круга, используя в ней поле с типом, ранее определенным для точки (сделать «структуру в структуре»). Оба подхода вполне приемлемы, однако ООП предлагает иной подход, который по ряду причин является гораздо более предпочтительным.

Объектно-ориентированный стиль позволяет определить новый объект как потомок другого ранее определенного типа. Это означает, что новый тип автоматически получает все поля и методы ранее введенного типа, который в этом случае называется предком или родительским типом. В этом случае в определении типа-потомка должно быть указано (в круглых скобках после служебного слова object) имя родительского типа:

type

TCircle = object ( TPoint )

Radius : integer

end;

Задание родительского типа означает, что в объектовом типе TCircle неявно присутствуют все поля типа TPoint; аналогично, для переменной этого нового типа доступны все методы из TPoint:

var

OneCircle : TCircle ;

begin

OneCircle.Create(100, 200) ;

OneCircle.Radius := 30 ;

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

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

type

TRing = object ( TCircle )

Radius2 : integer ;

end ;

Тип TRing наследует поле Radius из своего непосредственного родителя TCircle, а также поля и методы из типа TPoint, который также считается (косвенным) предком для TRing.

Длина такой цепочки наследования в языке никак не ограничивается.

Вернемся к примеру с объектным типом TCircle. По правилу наследования тип TCircle имеет в своем составе методы объекта-предка TPoint. Однако, легко видеть, что если методы GetX, GetY, возвращающие текущие значения координат, вполне применимы в типе-потомке, то, например, методы SwitchOn, SwitchOff не подходят для рисования круга. Поэтому, наряду с основным полем Radius, полное описание типа TCircle должно содержать также собственные методы для рисования и удаления круга и его передвижения по экрану. Самым простым решением было бы ввести в новый тип такие методы, дав им некоторые новые имена. Но объектно-ориентированный подход позволяет определить новые методы со старыми именами, переопределивтем самым методы типа-родителя:

Type

TCircle = object ( TPoint )

Radius : integer ;

procedure Create ( a , b , R : integer ) ;

procedure SwitchOn ;

procedure SwitchOff ;

procedure Move ( dx , dy : integer ) ;

function GetR : integer ;

end ;

 

procedure TCircle.Create ( a , b , R : integer ) ;

begin

TPoint.Create ( a , b ) ;

Radius := R

end ;

 

procedure TCircle.SwitchOn ;

begin

Visible := true ;

Graph.Circle ( X , Y , Radius )

end ;

 

function TCircle.GetR ; integer ;

begin

GetR := Radius

end ;

 

Такое определение объектного типа TCircle содержит следующие элементы:

- поля X, Y, Visible, унаследованные от родительского типа TPoint;

- собственное поле Radius;

- метод TCircle.Create, который инициализирует поля TCircle. Этот метод переопределяет внутри типа TCircle унаследованный от TPoint метод TPoint.Create. Для инициализации полей X, Y, Visible используется вызов метода TPoint.Create, который доступен (унаследован) в типе TCircle;

- методы TCircle.SwitchOn, TCircle.SwitchOff и TCircle.Move (два последних метода из экономии места не показаны), которые, соответственно, рисуют и удаляют круг и передвигают его. Эти методы полностью переопределяют (замещают) одноименные методы из TPoint, что имеет очевидный смысл: для рисования круга требуется иной алгоритм, нежели для рисования точки (в примере для этих целей используются стандартные процедуры из модуля Graph);

- новый (собственный) метод GetR для доступа к текущему значению радиуса круга;

- два унаследованных (и не переопределенных) метода GetX, GetY для получения текущих координат центра круга.

Сразу отметим, что переопределять можно только методы. Поля, указанные в родительском типе, безусловно наследуются типом-потомком и не могут быть в нем переопределены (то есть имена полей потомка не должны совпадать с именами полей типа-предка). Кроме того, новый метод в типе-потомке может иметь совершенно другие параметры, нежели одноименный метод из типа-предка.

Рассмотрим теперь поведение объекта типа TCircle, то есть вызовы его методов. Пусть имеется следующий фрагмент программы:

var

OneCircle : TCircle ;

begin

with OneCircle do

begin

Create ( 100 , 200 , 30 ) ;

SwitchOn ;

Writeln ( GetX , GetY )

end ;

end ;

Методы Create и SwitchOn определены в объекте OneCircle, поэтому их вызовы однозначно обрабатываются компилятором. Что касается методов GetX, GetY, то они не определены непосредственно в типе Circle, к которому принадлежит объект OneCircle. В таком случае компилятор, не обнаружив вызываемого метода, просматривает определение родительского типа; если данный метод и здесь не определен, то аналогично просматривается родитель родителя и т.д. Если очередной объектовый тип не имеет предка, а метод не обнаружен, то компилятор фиксирует ошибку.

Механизм наследования, являясь достаточно простым для понимания и использования, представляет широкие возможности при разработке программ. Имея несколько «базовых» объектовых типов (например, в интерфейсном разделе модуля), можно на их основе конструировать новые объекты, добавляя в них новые поля и расширяя и/или переопределяя соответствующие методы.

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

OnePoint := OneCircle ;

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