Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать
  1. Наследование

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

Таблица. 1. Необходимые сведения по категориям людей

Категория людей

Сведения

Студент

Фамилия Имя Отчество

Номер паспорта

Номер зачетки

Группа

Специальность

Сотрудник

Фамилия Имя Отчество

Номер паспорта

Табельный номер

Должность

Преподаватель

Фамилия Имя Отчество

Номер паспорта

Табельный номер

Кафедра

Преподаваемые дисциплины

Анализируя эту таблицу, можно сделать следующий вывод. Все категории людей имеют фамилию, имя, отчество и номер паспорта. Это связано с тем, что все они люди. Также следует отметить, что и сотрудники, и преподаватели имеют табельный номер. Это объясняется тем, что все они работают в учебном заведении. С другой стороны, имеются данные уникальные для каждой категории лиц.

Найденная общность показана на рис. 7.

Таким образом, мы выделили пять категорий или классов людей, которые имеют что-то общее, но чем-то различаются. Связь, названная на рис. 7 «является» в объектно-ориентированном программировании носит название «Наследование».

Наследование – это такое отношение между классами, когда один класс повторяет структуру и поведение другого класса (одиночное наследование) или других классов (множественное наследование).

Класс, структура и поведение которого наследуется, называется предком или родительским классом или базовым классом или суперклассом.

Класс, который наследует структуру и поведение, называется потомком или дочерним классом или производным классом или подклассом.

Схему, подобную показанной на рис. 7, называют иерархией наследования.

Наследование – это второй основополагающий принцип объектно-ориентированного программирования. Первый – инкапсуляция.

В языке Object Pascal допустимо только одиночное наследование классов. Объявление класса, который является потомком другого класса, имеет вид:

TNewObject = class(TOldObject) ... end;

где TNewObject – класс-потомок, TOldObject – класс-предок.

В Object Pascal все классы являются потомками класса TObject. Поэтому, если строить дочерний класс прямо от TObject, то в определении его можно не упоминать. Следующие выражения эквивалентны:

TMyObject = class(TObject) ... end;

TMyObject = class ... end;

Первый вариант, хотя и более длинный, предпочтительнее – для устранения возможных неоднозначностей. Во всех рассмотренных выше примерах все классы были производными от TObject. Подробно с классом Tobject можно ознакомиться в справочной системе Borland Delphi.

Класс-потомок наследует все поля, методы и свойства, которые были в классе-предке. Кроме этого он может объявлять собственные поля методы и свойства, но не может удалить ничего из того, что было объявлено в классе-предке. Рассмотрим, что происходит с полями, методами и свойствами при наследовании более подробно:

Поля

Все унаследованные от класса-предка поля доступны в дочернем классе. Также новый класс может объявить новые поля. Если имеет место совпадение имен полей в классе-предке и классе-потомке, то говорят, что они перекрываются. Типы нового и старого полей никак не взаимосвязаны и могут совпадать или различаться. Новое поле, объявленное в потомке скроет старое одноименное. Из методов класса-потомка по этому имени будет доступно поле, объявленное в классе-потомке. Поле, объявленное в классе-предке, из методов потомка непосредственно доступно не будет. Тем не менее, при создании объекта класса-потомка память будет отводиться под оба поля, и методы класса-предка по-прежнему будут работать с полем, объявленным в предке.

Для примера рассмотрим класс человек TPerson и производный от него класс студент TStudent:

Interface type

TPerson = class(TObject) Name: string; //Фамилия Age: integer; //Возраст Number: string; //Номер паспорта ... end;

TStudent = class(TPerson) Subject: string; //Изучаемая специальность Number: integer; //Номер зачетной книжки ... end;

Var Student: TStudent;

В этом примере в классе TStudent объявлены два собственных поля. Три поля он унаследовал от класса TPerson и, возможно, какие-то поля он унаследовал от предка класса TPerson – TObject. Распределение памяти под поля объекта Student типа TStudent показано на рис. 8. Методы, объявленные в классе TPerson будут работать с полем Number типа string, в котором хранится номер паспорта, а методы, объявленные в классе TStudent будут работать с полем Number типа integer – номер зачетной книжки. Номер паспорта в методах класса TStudent непосредственно доступен не будет, но будет доступен опосредовано, через вызов методов TPerson.

Рекомендуется без особой необходимости не перекрывать поля класса-предка. Например, в рассмотренной выше ситуации, следовало бы использовать названия полей: PassportNumber и ExamsBookNumber.

Методы

Унаследованные от класса-предка методы доступны в дочернем классе. Также класс-потомок может объявить новые методы. Если имеет место совпадение имен методов в производном и базовом классах, то говорят, что такие методы перекрываются. По типу перекрытия методы делятся на две группы: 1 - статические, 2 - виртуальные и динамические. Рассмотрим пока только статические методы. По умолчанию методы объекта являются статическими. Т.е. если при описании метода не было использовано никаких специальных служебных слов, то он будет статическим. При перекрытии статических методов код нового метода полностью заменяет собой код старого метода. При статическом перекрытии можно без ограничений изменять тип методов, то есть тип возвращаемого значения, количество и типы параметров – это полностью новый метод.

Доступ к перекрытым методам класса-предка из методов класса-потомка возможен. Для этого используется зарезервированное слово inherited:

Procedure TMyObject.SomeMethod(a: integer); begin //Будет вызван метод SomeMethod класса-предка inherited SomeMethod; ... end;

Рассмотрим пример. В этом примере разные методы с одинаковым именем SetData присваивают значения разным полям с именем Data. Причем в классе-потомке поле Data класса-предка непосредственно недоступно, хотя и доступно через соответствующие методы. Обратите внимание на то, что при распределении памяти под объект учитываются все поля всех классов-предков и создаваемого класса, в том числе и имеющие перекрывающиеся имена.

interface

type

TSampleObject1 = class(TObject) private Data: real; public Procedure SetData(AValue: real); ... end;

TSampleObject2 = class(TSampleObject1) private Data: integer; public procedure SetData(AValue: integer); ... end;

... implementation procedure TSampleObject1.SetData(AValue: real); begin Data := AValue; end;

procedure TSampleObject2.SetData(AValue: integer); begin Data := AValue; //вызовем перекрытый метод класса-предка inherited SetData(AValue*0.1); end;

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

Например:

Interface type

TSample1 = class private Fx: integer; Fangle: real; public function GetResult: real; //возвращает результат procedure SetValue(x: integer; angle: real); //устанавливает координату и угол angle в радианах end;

TSample2 = class(TSample1) public function GetResult: real; //возвращает результат procedure SetValue(x: integer; angle: real); //устанавливает координату и угол angle в градусах end;

implementation

function TSample1.GetResult: real; begin result := Fx*sin(Fangle); end;

procedure TSample1. SetValue(x: integer; angle: real); begin Fx := x; Fangle := angle; end;

function TSample2.GetResult: real; begin //поскольку это функция, несмотря на отсутствие параметров //имя функции после inherited следует указывать result := inherited GetResult; //добавим проверку if result<0 then result := 0; end;

procedure TSample2.SetValue(x: integer; angle: real); begin //переводим величину из градусов в радианы angle := angle/180*Pi;//изменяем параметр метода а не поле inherited; //будет вызвана процедура SetValue с параметрами x, angle //имя метода может не указываться end;

procedure Test; var Ob: TSample2; begin Ob := TSample2.Create; Ob.SetValue(10,180); ... Ob.Free; end;

В этом примере в классе-потомке TSample2 перекрываются методы SetValue и GetData. Вызов перекрытого метода предка GetData выполняется с указанием его имени, поскольку это функция. При вызове перекрытого метода SetValue из одноименного метода потомка его имя опускается. При этом передаются параметры x и angle. Передаваемое значение параметра x совпадает с полученным, передаваемое значение параметра angle отличается от полученного. В данном случае величина преобразована из градусов в радианы. То есть, процедура TSample1.SetValue будет вызвана с параметрами (10, 3.14). Можно было бы явно указать имя метода и параметры после inherited:

procedure TSample2.SetValue(x: integer; angle: real); begin //переводим величину из градусов в радианы angle := angle/180*Pi; inherited SetValue(x, angle); end;

Рекомендуется без особой необходимости не использовать статическое перекрытие методов. Желательно давать разным методам различающиеся имена.