Delphi. Урок 22. Наследование классов

Уроки для начинающих   25 Октябрь 2013  Автор статьи: admin 

Наследование — это еще одна очень важная парадигма в объектно-ориентированном программировании, которая применяется программистами очень широко. Итак, что же представляет собой наследование? Для того чтобы ответить на этот вопрос, перейдем к несложному примеру. Предположим, у нас существует три класса: человек, ученик и преподаватель. Согласитесь, что класс «человек» на уровень более общий, чем классы «ученик» и «преподаватель», т.е. «ученик» и «преподаватель» — это по сути и те же экземпляры класса «человек», только имеющие дополнительные, характерные для них самих, методы и поля. Как раз для того, чтобы один класс мог перенять поля и методы у другого и содержать набор собственных полей и методов были придуманы принципы наследования классов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type

THuman = class
public
  FirstName, LastName: string;
  YearOfBirth: integer;
  Height: integer;

  function Age: byte;

  constructor Create(NewFirstName, NewLastName: string);
end;

TTeacher = class(THuman) // В скобках после ключевого слова class
// указывается родительский класс, методы и поля которого
// будут также доступны из этого класса TTeacher
public
  Subject: string;

  constructor Create(NewFirstName, NewLastName, NewSubject: string);
end;

TStudent = class(THuman) // В скобках после ключевого слова class
// указывается родительский класс, методы и поля которого
// будут также доступны из этого класса TStudent
public
  ClassNumber: byte;
  Marks: array [0..10] of byte;
  Teachers: array [0..10] of TTeacher;

  constructor Create(NewFirstName, NewLastName: string; NewClassNumber: byte);
end;

Таким образом мы получили новые классы, основанные на базе класса THuman, имеющие те же самые поля FirstName, LastName, YearOfBirth, метод Age и т.д, но только с какими-то дополнительными своими полями (возможно и методами), которые характерны уже для данного класса.
[note]Родительский класс — класс, от которого был унаследован другой класс, перенявший описание и реализацию у первого. В нашем случае родительским классом является класс THuman.[/note]
[note]Дочерний класс — класс, который унаследовал описание и реализацию какого-либо родительского класса. Относительно родительского класса такой класс и называется дочерним. У нас это классы TTeacher и TStudent.[/note]
При обращении к классу TTeacher или TStudent, в нем будут доступны также все поля и методы из класса THuman, но с учетом областей доступа. Если существуют некоторые поля или методы у родительского класса в группе доступа private (и тем более strict private), то они будут невидимы при попытке к ним обратиться из дочернего класса. Хотя, использование родительскими методами этих приватных полей и методов, в том случае если к этим родительским методам есть доступ, ничем не ограничено, т.к. эти методы реализованы не в этом классе, а в родительском, где есть необходимый доступ. Интересно в данном случае обратить внимание на реализацию конструктора Create у классов TTeacher и TStudent:

1
2
3
4
5
constructor TStudent.Create(NewFirstName, NewLastName: string; NewClassNumber: byte);
begin
  inherited Create(NewFirstName, NewLastName);
  ClassNumber := NewClassNumber;
end;

Таким образом мы можем вызывать именно родительские методы, т.к. в данном случае метод TStudent.Create перекрывает THuman.Create. Ну и соответственно для такого вызова мы воспользуемся ключевым словом inherited. Сначала пишем inherited, а затем указываем название родительского метода и параметры. Будет вызван сначала конструктор родительского класса, который создаст объект в качестве THuman, а затем уже произойдет присвоение поля ClassNumber, характерное только для TStudent.
[tip]Немножечко отвлечемся от текущей темы, и рассмотрим более простой пример, когда у одного класса может существовать несколько конструкторов. Да, такое возможно, впрочем как и несколько деструкторов. Например, если они будут иметь разные названия. Если потребуется реализовать один конструктор B на базе другого A, уже существующего в этом классе, то для этого нужно всего-лишь вызвать другой конструктор A в нужном месте нашего нового конструктора B. Прямо как в предыдущем примере, только без слова inherited. Никаких присвоений делать не надо. Конструктор A выполнит создание объекта согласно тому, как это создание в нем реализовано. А затем уже в конструкторе B мы можем, к примеру, присвоить другие поля. Можем даже рассмотреть такой пример. Добавив в наш класс TStudent еще один конструктор:

1
constructor CreateWithFirstMark(NewFirstName, NewLastName: string; NewClassNumber: byte; FirstMark: byte);

Реализация нового метода тогда будет выглядеть так:

1
2
3
4
5
6
7
constructor TStudent.CreateWithFirstMark(NewFirstName, NewLastName: string; NewClassNumber: byte; FirstMark: byte);
begin
  Create(NewFirstName, NewLastName, NewClassNumber); // Вызываем сначала базовый конструктор
  // Он в свою очередь у нас еще создаст объект в качестве THuman и присвоит ClassNumber
  // Ну а теперь присвоим первую оценку ученику
  Marks[0] := FirstMark;
end;

[/tip]
Фактически, когда в TStudent.Create мы приписали слово inherited, мы просто хотели обратиться конкретно к родительскому конструктору. Но так делать следует только в том случае, когда какое-то имя какого-либо метода из текущего класса перекрывает метод родительского класса. Мы и указываем inherited, чтобы вызвать именно метод родительского класса. Но в остальных случаях, когда имя метода (или поля) не перекрывается, то метод (или поле) родительского класса можно вызвать также, словно он был объявлен в дочернем классе (главное, чтобы этот метод или поле не были закрыты группой доступа в родительском классе):

1
2
3
4
5
6
7
8
constructor TStudent.Create(NewFirstName, NewLastName: string; NewClassNumber: byte);
begin
  inherited Create(NewFirstName, NewLastName);
  ClassNumber := NewClassNumber;

  YearOfBirth := 1996; // Обращение к полю родительского класса
  ShowMessage('Ученику '+NewFirstName+' '+NewLastName+' '+IntToStr(Age)+' лет'); // Обращение к методу Age родительского класса
end;

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

Группа доступа protected

Как я уже обещал в предыдущем уроке, при изучении принципов наследования классов, у нас появится еще одна группа доступа. Называется она protected. Она схожа с группой доступа private, и единственное различие состоит в том, что перечисленные в этой группе доступа методы и поля будут доступны еще и из дочерних классов. Т.е. только внутри класса, в котором эти методы и поля были объявлены, а также в дочерних классах.
Ну и соответственно существует еще одна группа доступа «strict protected», видимость которой не распространяется в текущем модуле, т.е. так же как и «strict private» по сравнению с обычным «private».
Ну и в очередной раз говорить, что разобранная в данном уроке парадигма ООП очень полезна и открывает довольно много возможностей, бессмысленно — это и так понятно. К тому же, опять-таки, она крайне удобна в командной работе, т.к. позволяет программистам, к примеру, договориться о реализации родительского класса, а затем уже каждый сможет использовать эту реализацию класса и расширять ее в дочерних классах, имея при этом такие возможности.

Научиться программировать

  • на Delphi

  • на Java

  • на C++