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