Міністерство освіти та науки України
НУ „Львівська політехніка”
Лекція №5
з курсу: «Застосування засобів об’єктно-орієнтованого програмування в лінгвістичних задачах»
Львів - 2010
4.4.2 Спадкування1
1 Шупрута В.В. Delphi 2005. Учимся програмировать. Стр. 159
Концепція об’єктно-орієнтованого програмування передбачає
можливість визначати нові класи за допомогою додавання полів,
властивостей і методів до вже існуючих класів. Такий механізм
отримання нових класів називається новоутворенням. При цьому новий,
новутворений клас (називається нащадком) спадковує властивості і методи
свого базового, батьківського класса.
Таким чином спадкування (Inheritance) – це процес, за допомогою
якого один об’єкт може отримувати властивості іншого. Об’єкт може
спадковувати основні властивості іншого об’єкта і добавляти до них риси,
характерні тільки для нього. Спадкування є важливим, оскільки воно
дозволяє підтримувати конціпцію ієрархії класів. Використання ієрархії
класів робить керованими великі потоки інформації. Наприклад,
представимо опис житлового будинку. Будинок - це частина загального
класу, що називається будови. З іншого боку, будова - це частина більш
загального класу - конструкції, що є частиною ще більш загального класу
об’єктів, який можно назвати створенням рук людини. В кожному випадку
новоутворенний клас спадковує всі властивості що пов’язані з батьком і
додає до них свої власні, характеристики. Без використання ієрархії класів
для кожного об’єкта необхідно було задати всі характеристики, які би його
визначали. Однак при використанні спадкування можна описати об’єкт
шляхом визначення того спільного класу (або класів), до якого від
відноситься, з тими спеціальними рисами, які роблять об’єкт унікальним.
При оголошенні класа-нашадка вказують клас батька.
Наприклад, клас TEmployee [емплоии](конкретний співробітник
компанії) може бути утворений від розглянутого попередньо класу
TPersonal шляхом додавання поля FDepartment (відділ). Оголошення
класу TEmployee в цьому випадку може бути, як показано в лістінгу 4.7.
Лістінг 4.72 Оголошення класу-нашадка TEmployee
2 Шупрута В.В. Delphi 2005. Учимся програмировать. Стр. 159
TEmployee = class(TPersonal)
// Клас TEmployee створений на основі TPersonal.
private
FDepartment: byte; // Номер відділу співробітника.
function GetDepartment: byte;
procedure SetDepartment(New_Department: byte);
public
constructor Create(Name:TName; Age:TAge; Department: byte);
property Department:byte
read GetDepartment
write SetDepartment;
end;
Ім’я класу Tpersonal, що заключено в дужки показує, що клас
Temployee є похідним від класу TPersonal. В свою чергу клас TPersonal
є базовим для класу TEmployee.
Клас TEmployee повинен мати свій власний конструктор, який
забезпечує ініціалізацію класа-батька і своїх полів. В лістінгу 4.8
наведений приклад реалізації конструктора класу TEmployee.
Лістінг 4.8 Конструктор для класу TEmployee
// Конструктор для класу TEmployee.
constructor TEmployee.Create(Name:TName;Age:TAge;Department:byte);
begin
inherited Create(Name, Age); // Ініціалізація конструктора
// класа-батька.
FDepartment := Department;
end ;
В наведеному прикладі директивою inherited викликається
конструктор батьківського класу. Після цього присвоювання значення полю
класу-нащадка.
Після створення об’єкта похідного класу в програмі можна
використовувати поля і методи батьківського класу. В лістінгу 4.9
наведений текст програми, демонструє цю можливість.
Лістінг 4.9 Приклад реалізації принципу спадкування
unit WinForm;
interface
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data;
type
TName = string[30];
TAge = byte;
TWinForm = class(System.Windows.Forms.Form)
{$REGION 'Designer Managed Code1}
strict private
Components: System.ComponentModel.Container;
Button1: System.Windows.Forms.Button;
procedure InitializeComponent;
procedure Button1Click(sender: System.Object;e: System.EventArgs);
{$ENDREGION}
strict protected
procedure Dispose(Disposing: Boolean); override;
private
{ Private Declarations }
public
constructor Create;
end;
// Оголошення классу.
TPersonal = class
private
fname:TName; // Значення властивості Name – ім’я співробітника.
fage:TAge; // Значение свойства Age - вік співробітника.
function GetName:TName;
function GetAge:TAge;
procedure SetAge(new_age:TAge);
public
// Конструктор - створення нового об’єкта (экземпляра класса)
constructor Creat e(Name:TName;Age:TAge);
procedure Showlnfo; // Цей метод класу - показує інформацію
// про співробтника.
// Властивість об’єкта
property Name:TName // Властивість тілько для читання.
read GetName;
property Age:TAge // Властивість для зчитeвання та запису.
read GetAge
write SetAge;
end;
// Клас TEmployee створений на основі TPersonal.
TEmployee = class(TPersonal)
private
FDepartment:byte; // Номер відділу співробітника.
function GetDepartment:byte;
procedure SetDepartment(New_Department:byte);
public
constructor Create(Name:TName;Age:TAge;Department:byte);
property Department:byte
read GetDepartment
write SetDepartment;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForm))]
implementation
{$AUTOBOX ON}
{$REGION 'Windows Form Designer generated code'}
procedure TWinForm.Dispose(Disposing: Boolean);
begin
if Disposing then
begin
if Components <> nil then
Components.Dispose() ;
end ;
inherited Dispose(Disposing);
end ;
// Конструктор для форми.
constructor TWinForm.Create;
begin
inherited Create;
InitializeComponent;
end;
procedure TWinForm.Button1Click(sender: System.Object;
e: System.EventArgs);
var
worker: TPersonal;
dep_worker:TEmployee;
begin
// Створення нового об’єкта TPersonal.
worker:=TPersonal.Create('Шупрута Володимир',0);
// Виклик метода об’єкта - вивід інформації про співробітника.
worker.showinfо; // На екрані повідомлення Шупрута Володимир О'.
// Зміна властивості об’єкта.
worker.age:=2 5;
// Виклик метода об’єкта - вивід інформації про співробітника.
worker.showinfо // На екрані повідомлення 'Шупрута Володимир 25'.
// Створення співробітника - нащадка TEmployee.
dep_worker:=TEmployee.Create('Захаров Сергій',24,1);
// Відображення інформації - метод успадкований від TPersonal.
dep_worker.showinfо; // На екрані повідомлення 'Захаров Сергій 24'
// Відображення інформації - поля Name, Age і Department.
messagebox.Show(dep_worker.Name + ' '+ dep_worker.age.tostring+ ' '
+ dep_worker.FDepartment.tostring);
// Видалення об’єктів.
worker.free;
dep_worker.free;
end ;
// Конструктор для класу TPersonal.
// При виклику створюється новий об’єкт з ім’ям Name і вік Age.
constructor TPersonal.Create(Name:TName;Age:TAge);
begin
inherited Create;
fname:=Name;
fage:=Age;
end ;
// Метод отримання значення властивості Name.
function TPersonal.GetName;
begin
result:=fname;
end;
// Метод отримання значення властивості Age.
function TPersonal.GetAge;
begin
result:=fage
end;
// Метод запису значення властивості Age.
procedure TPersonal.SetAge(new_age:TAge);
begin
// Приклад обробки значення на запис.
// Якщо значення, що присвоюється значенню, <20, то
// властивість не змінить своего значення.
if new_age<2 0
then exit
else fage:=new_age;
end;
// Метод класу TPersonal - процедура показує дані про співробітника.
procedure TPersonal.showinfо;
begin
messagebox.Show(Name+' '+Age.Tostring) ;
end;
// Конструктор для класу TEmployee.
constructor
TEmployee.Create(Name:TName;Age:TAge;Department:byte);
begin
inherited Create(Name,Age); //Ініціалізація конструктора
// класа-батька.
FDepartment:=Department;
end;
// Метод отримання значеня властивості Department.
function TEmployee.GetDepartment;
begin
result:=FDepartment;
end ;
// Метод запису значення в властивості Department.
procedure TEmployee.SetDepartment(New_Department:byte);
begin
FDepartment:=New_Department;
end;
end.
Директива inherited «каже» компілятору, що треба викликати метод
базового (батьківського) класу з тим самим ім’ям, що і метод, в якому вона
вказана. При цьому якщо сигнатура методів батьківського класу і його
нащадків співпадає (нагадаємо, що сигнатурою методу є його ім’я і набір
параметрів), то вказувати повний формат виклику не треба. Наприклад,
створимо нащадка класу TEmployee, який відрізняється від свого батька
тільки тим, що при виклику конструктора зразу викликається повідомлення
з інформацією з полей об’єкта:
TNewEmployee = class(TEmployee)
public
constructor Create(Name:TName;Age:TAge;Department:byte);
end;
constructor
TNewEmployee.Create(Name:TName;Age:TAge;Department:byte);
begin
inherited;
Showlnfo;
end;
Як бачимо, в конструкторі вказується директива inherited, a
компілятор сам підставляє за нею виклик конструктора батьківського
об’єкта з правильними параметрами.
При розгляді лістінга 4.9, а точніше – опису об’єкта TPersonal і
реалізації його конструктора - може виникнути наступне питання: чому, в
описі об’єкта відсутне вказівка класу-батька, а в конструкторі ми
звертаємось до конструктора базового класу?
Справа в тому, що в Delphi всі класи вишикувані в єдину ієрархію, на
вершині якої стоїть клас TObject. Коли при описі класу пропускається
вказівка його батька, то в цьому випадку компілятор рахує, що батьком
такого класу є TObject.
4.4.3 Поліморфізм3
3 Шупрута В.В. Delphi 2005. Учимся програмировать. Стр. 165
З попереднього прикладу ми, побачили одну важливу особливість.
Клас TEmployee спадковує метод Showlnfo класу TPersonal, але
даний метод для класу TEmployee не дуже корисний, в зв’язку з тим що
відображається тільки частина інформації – ім’я і вік. Бажано, щоб, при
використанні даного методу для класу TEmployee відображалася повна
інформація – ім’я, вік та відділ. Така заміна методів при відсутності
зовнішніх відмінностей у викликах може бути реалізована за допомогою
третьої концепції ООП - поліморфізма.
Поліморфізм (Polymorphism) - це можливість використовувати
однакові імена для методів, що входять в різні класи. Концепція
поліморфізма забезпечує у випадку використання методів до об’єкту
використання іменно того методу, який відповідає класу об’єкта.
З попереднього прикладу в нас визначені два класи: TPersonal і
TEmployee, причому перший є базовим для другого.
В базовому класі визначений метод Showlnfo, що забезпечує вивід
інформації (ім’я і вік) про співробітника на екран. Щоб дочірній клас
(нащадок) міг використовувати метод з тим самим ім’ям, зміст якого
складали би деякі інші дії, даний метод в базовому класі треба оголосити з
директивю virtual. Оголошення методу віртуальним дає можливість
дочірньому класу здійснити заміну віртуального методу своїм власним.
Нижче в лістінгу 4.10 наведений приклад опису базового класу з
використанням директиви virtual.
Лістінг 4.10 Метод showinfо оголошений в базовому класу як
віртуальний
// Оголошення класу.
TPersonal = class
private
fname:TName; // Значення властивості Name – ім’я співробітника.
fage:TAge; // Значення властивості Age - вік співробітника.
function GetName:TName;
function GetAge:TAge;
procedure SetAge(new_age:TAge);
public
// Конструктор - створення нового (екземпляра класу).
constructor Create(Name:TName;Age:TAge);
// Це метод класу - показує інформацію про співробітника –
// віртуальний.
procedure Showinfо; virtual;
// Свойства объекта
property Name:TName // Свойство только для чтения.
read GetName;
property Age:TAge // Свойство для чтения и записи.
read GetAge
write SetAge;
end;
В дочірньому класі TEmployee також визначений свій метод Showlnfo
(лістінг 4.11), який замещає відповідний метод батківського класу. Метод
породженого класу, що заміщає віртуальний метод батківського класу,
помічаєтся директивою override.
Лістінг 4.11 Метод showinf о в описании класса TEmployee помечен
директивою override
/ / Класс TEmployee создан на основе TPersonal.
TEmployee = class(TPersonal)
FDepartment:byte; // Номер отдела сотрудника.
function GetDepartment:byte;
procedure SetDepartment(New_Department:byte);
public
constructor Create(Name:TName;Age:TAge;Department:byte);
procedure showinfо; override; // Метод Showlnfo заменен
// на собственный.
// Свойства объекта.
property Department:byte
read GetDepartment
write SetDepartinent ;
end;
4.4.4. Для чого потрібні директиви protected і
private(додатково)
Крім оголошених елементів класа (полів, методів, властивостей) опис
класу, як правило, містить директиви protected (захищений) і private
(приватними), які встановлюють ступінь видимості елементів класу в
програмі.
Елементи класу, оголошенні в секції protected, доступні тільки в
порожденных від нього класах. Область видимості елементів класу цієї
секції не обмежується модулем, в якому знаходиться опис класу.
Зазвичай в секцію protected розміщюють опис методів класу.
Елементи класу, оголошенні в секції private, видимі тільки всередині
модуля. Ці елементи не доступні за межами модуля, навіть в похідних
класах. Зазвичай в секції private розміщають опис полів класу, а методи,
забезпечивають доступ до цих полів, розміщують в секцію protected.
В тих випадках, коли необхідно повністюю зкрити елементи класу,
виділення класу необхідно помістити в окремий модуль, а в програму, яка
використовує об’єкти цього класу, розмістити в секції uses вказати
посилання на цей модуль.
4.4.5. Області видимості елементів класу
Для розмежування доступу до властивостей і методів екземплярів класів
між різними частинами програми передбачені модифікатори доступу
(«видимості»), приведені в табл. 5.1.
Модифікатор доступу в Delphi (як і в Pascal, але на відміну від деяких
інших мов програмування) відноситься не до конкретної властивості або
методу класу, а до всіх елементів класу (властивостям і методам), опис
яких розташовується після вказівки модифікатора. Один і той же
модифікатор може указуватися в описі класу більше одного разу.
Порівняльна таблиця модифікаторів доступу в Pascal і Delphi Таблиця 5.1
Модифікатор
Область видимості властивості або методу, поміченого
модифікатором
Pascal
Delphi
private
опис класу і модуль
у якому описаний клас
у тому числі і для інших
класів
Так само
protected
не використовується
як у private + у класах
успадкованих від цього
класу
public
будь-яка частина
програми
Так само
published
не використовується
як у public + у
Інспекторові об'єктів
Delphi для візуальної
побудови програм
Стандартний опис класу містить властивості і методи, розташовані в
порядку зростання їх видимості, але це правило не є обов'язковим.
Турe
.....
<Ім'я класу> = class
private
<Ім'я властивості 1>: <Тип властивості 1>;
(Опис властивостей класу, що мають
область видимості private)
.....
<Ім'я властивості N>: <Тип властивості N>;
<Заголовок методу 1>;
{Опис методів класу, що мають
область видимості private}
.....
<Заголовок методу М>;
protected
<Ім'я властивості 1>: <Тип властивості 1>;
{Опис властивостей класу, що мають
область видимості protected}
<Ім'я властивості N>: <Тип властивості Nl>;
<3аголовок методу 1>;
{Опис методів класу, що мають
область видимості protected}
.....
<3аголовок методу М>;
public
<Ім'я властивості 1>: <Тип властивості 1>;
{Опис властивостей класу, що мають
область видимості public)
<Ім'я властивості N>: <Тип властивості N>;
<3аголовок методу 1>;
{Опис методів класу, що мають
область видимості public}
…....
<3аголовок методу М>;
published
(Опис спеціальних властивостей
класу (property), що мають область
видимості published}
End;
Різні області видимості властивостей і методів об'єкту призначені для
спрощення підтримки цілісності інформації в об'єктах. Розглянемо клас, в
якому містяться три властивості, — а, b і c, причому c є добутком а і b.
Якщо властивості а, b і с матимуть широку область видимості public, тобто
будуть доступні з будь-якого фрагмента програми, то об'єкт — екземпляр
такого класу може містити некоректні дані, оскільки існує можливість зміни
властивостей а і b без перерахунку властивості с. Для виходу з такої
ситуації можна запропонувати два підходи, і обидва вони зв'язані з
використанням областей видимості властивостей об'єкту.
Перший підхід (див. лістинг 5.5) полягає в тому, щоб приховати
властивості а і b від викликаючих підпрограм, призначивши ним вузькі
області видимості, наприклад private або protected. Вибір того або іншого
модифікатора визначається необхідністю використання приховуваних
властивостей в класах-нащадках даного класу. Якщо такої необхідності
немає, то призначається область видимості private, якщо об'єкти-нащадки
повинні мати прямий доступ до властивостей, то призначається область
видимості protected.
У будь-якому випадку, зовнішні підпрограми не зможуть звернутися до
властивостей а і b тому доведеться передбачити методи, які їх
встановлюють. У цих методах, крім установки значень властивостей,
повинен проводитися перерахунок властивості с. Це гарантуватиме
цілісність даних об'єкту при зміні властивостей а і b.
Відмітимо, що методи установки повинні мати широку область видимості,
щоб до них можна було звернутися з будь-якого місця програми.
Лістинг 5.5. Завдання областей видимості. Перший підхід
Unit UsingPrivatel; {Заголовок модуля}
Interface {Початок інтерфейсної частини)
Турe
ABC = class {Заголовок класу ABC)
private {Початок області видимості private}
а, b: Double; {Властивості а і Ь мають вузьку область видимості}
public (Початок області видимості public}.
c: Double; {Властивість з має широку область видимості)
Procedure SETA(NEWA: Double); {Метод SETA має широку область видимості}
Procedure SETB(NEWB: Double); {Метод SETB має широку область видимостi}
end;
Implementation (Початок описової частини)
Procedure ABC.SetA(NEWA: Double); {Опис методу. SETA класу ABC}
Begin
а := NEWA; (Установка значення властивості а}
c := а * b; (Оновлення значення властивості c — підтримка цілісності даних}
end;
Procedure ABC.SetB(NEWB: Double); {Опис методу SETB класу ABC}
Begin
b := NEWB; {Установка значення властивості b}
c := а * Ь; {Оновлення значення властивості c — підтримка цілісності даних}
end;
end. (Закінчення модуля}
При такій конструкції класу викликаюча підпрограма не зможе змінити
значення властивостей а і b, не вплинувши тим самим на значення
властивості c, тому цілісність даних не буде порушена. Проте властивість c
доступно для зміни, навіть якщо її значення перераховане при установці
значень а і b. У результаті воно може змінитися, викликавши
невідповідність даних. Таким чином, краще було б приховати властивість,
реалізувавши спеціальний метод доступу до його значення (лістинг 5.6).
Лістинг 5.6. Завдання областей видимості. Другий підхід
Unit UsingPrivate2; {Заголовок модуля}
Interface (Початок інтерфейсної частини}
Турe
Авс2 = class (Заголовок класу ABC}
Private (Початок області видимості private}
c: Double; (Властивість c має вузьку область видимості}
Public (Початок області видимості public}
а, b: Double; (Властивості а і b мають широку область видимості)
Function GETC: Double; (Метод GETC має широку область видимості}
end;
Implementation {Початок описової частини)
Function ABC2.GetC: Double; (Опис методу GETC класу Авс2}
Begin
c := а * Ь; {Оновлюємо значення властивості c)
Result := c; {Повертаємо значення із зухвалій підпрограмі}
end;
end. {Закінчення модуля}.
При такій конструкції класу цілісність даних не може бути на рушиться
викликаючими підпрограмами, якщо тільки вони не знаходяться в одному
модулі з описуваним класом, тобто, в даному випадку, в модулі
UsingPrivate2. Єдиним недоліком такого підходу є постійний перерахунок
значення с при кожному зверненні до методу GETC. Тому найбільш
оптимальним варіантом рішення наший завдання буде заховання всіх
властивостей класу і реалізація методів доступу до них через методи (див.
лістинг 5.7).
Лістинг 5.7. Завдання областей видимості. Третій підхід (оптимальний)
unit UsingPrivateS; {Заголовок модуля}
Interface
Турі
АВСЗ = class
private
а, b, с: Double; {Всі властивості мають вузьку область видимості}
public
Procedure SETA(NEWA: Double); {Всі методи мають широку область видимості}
Procedure SETB(NEWB: Double);
Function GETC: Double;
end;
Implementation
Procedure ABC3.SetA(NEWA: Double); {Опис методу SETA класу АВСЗ}
Begin
а := NEWA;
с := а * b;
end;
Procedure АВСЗ.SetB(NEWB: Double); {Опис методу SETB класу АВСЗ}
Begin
b := NEWB;
с := а * b;
end;
Function ABC3.GetC: Double; {Опис методу GETC класу АВСЗ)
Begin
Result := с; {Просто повертаємо значення с}
end;
end.
Питання:
Література:
1. Архангельский А.Я. Программирование в Delphi. Учебник по
классическим версиям Delphi. 2006 г. - 1152 с.
2. Архангельский А.Я. Delphi 2006. Справочное пособие: язык
Delphi, классы, функции Win32 и .NET, 2006 г. - 1152 с.
3. Архангельский А.Я. Приемы программирования в Delphi на
основе VCL, 2006 г. - 944 с.
4. Архангельский А.Я. Программирование в Delphi для Windows.
Версии 2006, 2007, Turbo Delphi, 2007 г. - 1248 с.
5. Гофман В. Э., Хомоненко А. Д. Delphi. Быстрый старт. — СПб.:
БХВ-Петербург, 2003. — 288 с: ил.
6. Шупрута В.В. Delphi 2005. Учимся програмировать.