Основы программирования — второй семестр 08-09; Михалкович С.С.; VI часть
Наследование
Введение
Наследование в программировании возникло как ответ на реальные отношения наследования классов в реальном мире и прикладных задачах.
————————————
Ввиду увеличения сложности задач в программировании, акцент переместился от алгоритмов к объектам, содержащим алгоритмы в качестве методов.
При этом, понятие главного алгоритма также потеряло свою важность. В больших проектах нет главного алгоритма или он тривиален, но есть большое число взаимосвязанных задач.
Пример. Главный алгоритм работы операционной системы.
* начальная инициализация * цикл обработки сообщений если сообщение пришло то обработать сообщение до сообщения «Конец» * заключительные действия
Как видим, этот алгоритм тривиален и ничего не говорит о том, как работает ОС.
————————————
Поскольку главный алгоритм сложной системы не существует или тривиален, то для программирования работы этой сложной системы мы:
- выявляем классы объектов, присутствующих в этой системе
- их свойства и методы
- выявляем то общее, что есть в различных классах
- выявляем различные взаимозависимости между классами и взаимодействие между объектами этих классов
Примеры взаимозависимостей и взаимодействия.
- класс содержит в качестве поля объект другого класса
- в методе класса параметром является объект другого класса
- метод класса вызывает статический метод другого класса
и т.д.
Замечание.
Взаимодействие классов устанавливается на этапе написания текста программы,
а взаимодействие конкретных объектов устанавливается на этапе выполнения программы
К отношениям между классами относится также то, при котором
один из классов является разновидностью другого.
Пример.
Эти классы (Student — SeniorStudent) называют:
- базовый — производный
- предок — потомок
- надкласс — подкласс
Все производные классы наследуют от базового:
- поля
- методы
- свойства
а также могут добавлять новые:
- поля
- свойства
- методы
и переопределять (замещать)
- некоторые методы базового класса
Каковы цели наследования?
- Повторное использование кода.
- Обеспечение вариабельности и изменчивости кода.
Наследование — это расширение или сужение?
Наследование — это расширение интерфейса класса, но сужение количества объектов (представителей) класса.
Наследование на примере Student - SeniorStudent
Про переменную Self см. здесь.
<xh4> Базовый класс Student </xh4>
interface
uses System;
const
/// Минимальный допустимый возраст студента
MIN_AGE = 1;
/// Максимальный допустимый возраст студента
MAX_AGE = 120;
/// Минимальный возможный курс
MIN_COURSE = 1;
/// Максимальный возможный курс
MAX_COURSE = 4;
type
/// Студент
Student = class
private
fName: string;
fAge, fCourse, fGroup: integer;
procedure SetName(Name: string);
procedure SetAge(Age: integer);
procedure SetCourse(Course: integer);
procedure SetGroup(Group: integer);
procedure IncAge;
public
/// Имя — только на чтение
property Name: string read fName;
/// Возраст — только на чтение
property Age: integer read fAge;
/// Курс — только на чтение
property Course: integer read fCourse;
/// Группа — только на чтение
property Group: integer read fGroup;
constructor Create(Name: string; Age, Course, Group: integer);
procedure NextCourse;
procedure Print;
procedure Println;
end;
implementation
procedure Student.SetName(Name: string);
begin
if Name <> '' then
fName := Name
else
raise new Exception(
'Попытка присвоить студенту пустое имя!');
end;
procedure Student.SetAge(Age: integer);
begin
if (Age >= MIN_AGE) and (Age <= MAX_AGE) then
fAge := Age
else
raise new Exception(
'Выход за границы диапазона допустимого возраста [' +
MIN_AGE.ToString + '..' + MAX_AGE.ToString + ']!');
end;
procedure Student.SetCourse(Course: integer);
begin
if (Course >= MIN_COURSE) and (Course <= MAX_COURSE) then
fCourse := Course
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + MAX_COURSE.ToString + ']!');
end;
procedure Student.SetGroup(Group: integer);
begin
if (Group > 0) then
fGroup := Group
else
raise new Exception(
'Попытка присвоить гурппе отрицательный номер!');
end;
procedure Student.IncAge;
begin
if fAge < MAX_AGE then
fAge += 1
else
raise new Exception(
'Выход за границы диапазона допустимого возраста [' +
MIN_AGE.ToString + '..' + MAX_AGE.ToString + ']!');
end;
constructor Student.Create(Name: string; Age, Course, Group: integer);
begin
SetName(Name);
SetAge(Age);
SetCourse(Course);
SetGroup(Group);
end;
procedure Student.NextCourse;
begin
if fCourse < MAX_COURSE then
begin
fCourse += 1;
IncAge;
end
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + MAX_COURSE.ToString + ']!');
end;
procedure Student.Print;
begin
WriteFormat(
'Имя: {0} Возраст: {1} Курс: {2} Группа: {3}',
fName, fAge, fCourse, fGroup);
end;
procedure Student.Println;
begin
Print;
writeln();
end;
<xh4> Производный класс SeniorStudent </xh4>
SeniorStudent — студент старших курсов.
Помимо имени, возраста, курса и группы:
- у него будет научный руководитель (Advisor)
- он будет знать тему курсовой работы, которую получает от своего научного руководителя
- и для него будет известно, какую он получает образовательную степень (бакалавра, специалиста или магистра)
Будем считать, что класс преподавателя у нас уже есть, и есть метод SayCourseWorkTheme(MyStudent: SeniorStudent): string.
Или сделаем пока заглушку:
type
/// Преподаватель
Teacher = class
private
/// Имя
fName: string;
procedure SetName(Name: string);
public
/// Имя — только на чтение
property Name: string read fName;
constructor Create(Name: string);
/// Возвращает тему курсовой работы для студента MyStudent
function SayCourseWorkTheme(MyStudent: SeniorStudent): string;
/// Выводит данные по преподавателю
procedure Print;
end;
implementation
procedure Teacher.SetName(Name: string);
begin
if Name <> '' then
fName := Name
else
raise new Exception(
'Попытка присвоить преподавателю пустое имя!');
end;
constructor Teacher.Create(Name: string);
begin
SetName(Name);
end;
function Teacher.SayCourseWorkTheme(MyStudent: SeniorStudent): string;
begin
result := 'Тема курсовой работы';
end;
procedure Teacher.Print;
begin
WriteFormat(
'Имя: {0} ',
fName);
end;
Приступим к реализации SeniorStudent.
Для начала нам понадобится вспомогательный тип EduModelType (образовательная степень), для лучшего использования которого напишем две функции:
function MaxCourseByEduModel(eduModel: EduModelType): integer — возвращающую максимальный курс, соответствующий образовательной степени eduModel
и function StringEduModel(eduModel: EduModelType): string — возвращающую строковое представление образовательной степени:
type
/// Образовательная степень (Бакалавр, Специалист, Магистр)
EduModelType = (Bachelor, Specialist, Magister);
/// Возвращает максимальный курс, соответствующий образовательной степени eduModel
function MaxCourseByEduModel(eduModel: EduModelType): integer;
/// Возвращает строковое представление образовательной степени
function StringEduModel(eduModel: EduModelType): string;
implementation
function MaxCourseByEduModel(eduModel: EduModelType): integer;
begin
case eduModel of
Bachelor: result := 4;
Specialist: result := 5;
Magister: result := 6;
end;
end;
function StringEduModel(eduModel: EduModelType): string;
begin
case eduModel of
Bachelor: result := 'Бакалавр';
Specialist: result := 'Специалист';
Magister: result := 'Магистр';
end;
end;
Теперь напишем интерфейс нашего старшекурсника:
/// Студент старших курсов
SeniorStudent = class (Student)
private
fAdvisor: Teacher;
fEduModel: EduModelType;
fCourseWorkTheme: string;
procedure SetAdvisor(Advisor: Teacher);
procedure SetEduModel(EduModel: EduModelType);
/// Устанавливает тему курсовой работы, получая её от научного руководителя
procedure GetCourseWorkTheme;
public
/// Научный руководитель — только на чтение
property Advisor: Teacher read fAdvisor;
/// Образовательная степень — только на чтение
property EduModel: EduModelType read fEduModel;
/// Тема курсовой работы — только на чтение
property CourseWorkTheme: string read fCourseWorkTheme;
constructor Create(Name: string; Age, Course, Group: integer;
Advisor: Teacher; EduModel: EduModelType);
procedure NextCourse;
procedure Print;
procedure Println;
end;
Как реализовать сеттеры, GetCourseWorkTheme и NextCourse мы знаем:
procedure SeniorStudent.SetAdvisor(Advisor: Teacher);
begin
fAdvisor := Advisor;
end;
procedure SeniorStudent.SetEduModel(EduModel: EduModelType);
begin
fEduModel := EduModel;
end;
procedure SeniorStudent.GetCourseWorkTheme;
begin
fCourseWorkTheme := fAdvisor.SayCourseWorkTheme(self);
end;
procedure SeniorStudent.NextCourse;
begin
var maxCourse := MaxCourseByEduModel(EduModel);
if fCourse < maxCourse then
begin
fCourse += 1;
IncAge;
end
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + maxCourse.ToString + ']!');
end;
Замечание. В процессе разработки возникла проблема доступа к приватным полям предка (fCourse)
Если Student и SeniorStudent реализованы в одном модуле, то такой проблемы нет и доступ возможен, однако, если они они реализованы в разных модулях, то данный код не откомпилируется.
Исправим это в дальнейшем, а пока будем считать, что классы реализованы в одном модуле.
Заметим, что процедура NextCourse была полностью переопределена в потомке.
Но, как правило, одноименный метод в потомке не переписывается полностью, а вызывает соответствующий метод предка.
Переопределяющий метод называется также заменяющим.
Для вызова в методе Method замещенного метода предка используется конструкция
inherited Method
Inherited — значит унаследованный.
Воспользуемся этим при реализации оставшихся методов:
constructor SeniorStudent.Create(Name: string; Age, Course, Group: integer;
Advisor: Teacher; EduModel: EduModelType);
begin
inherited Create(Name, Age, Course, Group);
fAdvisor := Advisor;
fEduModel := EduModel;
GetCourseWorkTheme;
end;
procedure SeniorStudent.Print;
begin
inherited Print;
write('Преподаватель: ');
fAdvisor.Print;
writeFormat('Тема курсовой работы: «{0}» ', CourseWorkTheme);
writeFormat('Образовательная степень: {0}', StringEduModel(EduModel));
end;
procedure SeniorStudent.Println;
begin
Print;
writeln();
end;
unit University;
interface
uses System;
const
/// Минимальный допустимый возраст студента
MIN_AGE = 1;
/// Максимальный допустимый возраст студента
MAX_AGE = 120;
/// Минимальный возможный курс
MIN_COURSE = 1;
// ============================================ Student ===========================================
const
/// Максимальный возможный курс
MAX_COURSE = 4;
type
/// Студент
Student = class
private
/// Имя
fName: string;
/// Возраст
fAge: integer;
/// Курс
fCourse: integer;
/// Группа
fGroup: integer;
procedure SetName(Name: string);
procedure SetAge(Age: integer);
procedure SetCourse(Course: integer);
procedure SetGroup(Group: integer);
procedure IncAge;
public
/// Имя — только на чтение
property Name: string read fName;
/// Возраст — только на чтение
property Age: integer read fAge;
/// Курс — только на чтение
property Course: integer read fCourse;
/// Группа — только на чтение
property Group: integer read fGroup;
/// <summary>
/// Создает нового студента
/// </summary>
/// <param name="Name">Имя (пустое недопустимо)</param>
/// <param name="Age">Возраст (отрицательный или больший MAX_AGE недопустим)</param>
/// <param name="Course">Курс (отрицательный или больший MAX_COURSE недопустим)</param>
/// <param name="Group">Группа (отрицательная недопустима)</param>
constructor Create(Name: string; Age, Course, Group: integer);
/// Переводит студента на следующий курс, если он меньше MAX_COURSE
procedure NextCourse;
procedure Print;
procedure Println;
end;
// ========================================== EduModelType ========================================
type
/// Образовательная степень (Бакалавр, Специалист, Магистр)
EduModelType = (Bachelor, Specialist, Magister);
/// Возвращает максимальный курс, соответствующий образовательной степени eduModel
function MaxCourseByEduModel(eduModel: EduModelType): integer;
/// Возвращает строковое представление образовательной степени
function StringEduModel(eduModel: EduModelType): string;
// ============================================ Teacher ===========================================
type
SeniorStudent = class; // предописание класса
/// Преподаватель
Teacher = class
private
/// Имя
fName: string;
procedure SetName(Name: string);
public
/// Имя — только на чтение
property Name: string read fName;
/// <summary>
/// Создает нового преподавателя
/// </summary>
/// <param name="Name">Имя (пустое недопустимо)</param>
constructor Create(Name: string);
/// Возвращает тему курсовой работы для студента MyStudent
function SayCourseWorkTheme(MyStudent: SeniorStudent): string;
procedure Print;
procedure Println;
end;
// ========================================= SeniorStudent ========================================
/// Студент старших курсов
SeniorStudent = class (Student)
private
/// Научный руководитель
fAdvisor: Teacher;
/// Образовательная степень
fEduModel: EduModelType;
/// Тема курсовой работы
fCourseWorkTheme: string;
procedure SetAdvisor(Advisor: Teacher);
procedure SetEduModel(EduModel: EduModelType);
/// Устанавливает тему курсовой работы, получая её от научного руководителя
procedure GetCourseWorkTheme;
public
/// Научный руководитель — только на чтение
property Advisor: Teacher read fAdvisor;
/// Образовательная степень — только на чтение
property EduModel: EduModelType read fEduModel;
/// Тема курсовой работы — только на чтение
property CourseWorkTheme: string read fCourseWorkTheme;
/// <summary>
/// Создает нового студента старших курсов
/// </summary>
/// <param name="Name">Имя (пустое недопустимо)</param>
/// <param name="Age">Возраст (отрицательный или больший MAX_AGE недопустим)</param>
/// <param name="Course">Курс (отрицательный или больший MAX_COURSE недопустим)</param>
/// <param name="Group">Группа (отрицательная недопустима)</param>
/// <param name="Advisor">Научный руководитель</param>
/// <param name="EduModel">Образовательная степень (бакалавр, специалист, магистр)</param>
constructor Create(Name: string; Age, Course, Group: integer;
Advisor: Teacher; EduModel: EduModelType);
procedure NextCourse;
procedure Print;
procedure Println;
end;
implementation
// ============================================ Student ===========================================
procedure Student.SetName(Name: string);
begin
if Name <> '' then
fName := Name
else
raise new Exception(
'Попытка присвоить студенту пустое имя!');
end;
procedure Student.SetAge(Age: integer);
begin
if (Age >= MIN_AGE) and (Age <= MAX_AGE) then
fAge := Age
else
raise new Exception(
'Выход за границы диапазона допустимого возраста [' +
MIN_AGE.ToString + '..' + MAX_AGE.ToString + ']!');
end;
procedure Student.SetCourse(Course: integer);
begin
if (Course >= MIN_COURSE) and (Course <= MAX_COURSE) then
fCourse := Course
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + MAX_COURSE.ToString + ']!');
end;
procedure Student.SetGroup(Group: integer);
begin
if (Group > 0) then
fGroup := Group
else
raise new Exception(
'Попытка присвоить гурппе отрицательный номер!');
end;
procedure Student.IncAge;
begin
if fAge < MAX_AGE then
fAge += 1
else
raise new Exception(
'Выход за границы диапазона допустимого возраста [' +
MIN_AGE.ToString + '..' + MAX_AGE.ToString + ']!');
end;
constructor Student.Create(Name: string; Age, Course, Group: integer);
begin
SetName(Name);
SetAge(Age);
SetCourse(Course);
SetGroup(Group);
end;
procedure Student.NextCourse;
begin
if fCourse < MAX_COURSE then
begin
fCourse += 1;
IncAge;
end
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + MAX_COURSE.ToString + ']!');
end;
procedure Student.Print;
begin
WriteFormat(
'Имя: {0} Возраст: {1} Курс: {2} Группа: {3} ',
fName, fAge, fCourse, fGroup);
end;
procedure Student.Println;
begin
Print;
writeln();
end;
// ============================================ Teacher ===========================================
procedure Teacher.SetName(Name: string);
begin
if Name <> '' then
fName := Name
else
raise new Exception(
'Попытка присвоить преподавателю пустое имя!');
end;
constructor Teacher.Create(Name: string);
begin
SetName(Name);
end;
function Teacher.SayCourseWorkTheme(MyStudent: SeniorStudent): string;
begin
result := 'Тема курсовой работы';
end;
procedure Teacher.Print;
begin
WriteFormat(
'Имя: {0} ',
fName);
end;
procedure Teacher.Println;
begin
Print;
writeln();
end;
// ========================================== EduModelType ========================================
function MaxCourseByEduModel(eduModel: EduModelType): integer;
begin
case eduModel of
Bachelor: result := 4;
Specialist: result := 5;
Magister: result := 6;
end;
end;
function StringEduModel(eduModel: EduModelType): string;
begin
case eduModel of
Bachelor: result := 'Бакалавр';
Specialist: result := 'Специалист';
Magister: result := 'Магистр';
end;
end;
// ========================================= SeniorStudent ========================================
procedure SeniorStudent.SetAdvisor(Advisor: Teacher);
begin
fAdvisor := Advisor;
end;
procedure SeniorStudent.SetEduModel(EduModel: EduModelType);
begin
fEduModel := EduModel;
end;
procedure SeniorStudent.GetCourseWorkTheme;
begin
fCourseWorkTheme := fAdvisor.SayCourseWorkTheme(self);
end;
constructor SeniorStudent.Create(Name: string; Age, Course, Group: integer;
Advisor: Teacher; EduModel: EduModelType);
begin
inherited Create(Name, Age, Course, Group);
fAdvisor := Advisor;
fEduModel := EduModel;
GetCourseWorkTheme;
end;
procedure SeniorStudent.NextCourse;
begin
var maxCourse := MaxCourseByEduModel(EduModel);
if fCourse < maxCourse then
begin
fCourse += 1;
IncAge;
end
else
raise new Exception(
'Выход за границы диапазона допустимых курсов [' +
MIN_COURSE.ToString + '..' + maxCourse.ToString + ']!');
end;
procedure SeniorStudent.Print;
begin
inherited Print;
write('Преподаватель: ');
fAdvisor.Print;
writeFormat('Тема курсовой работы: «{0}» ', CourseWorkTheme);
writeFormat('Образовательная степень: {0}', StringEduModel(EduModel));
end;
procedure SeniorStudent.Println;
begin
Print;
writeln();
end;
end.