Успадкування (програмування)
Успадкування (англ. inheritance) — це один з принципів об'єктно-орієнтовного програмування[1], який дає класу можливість використовувати програмний код іншого (базового) класу[2], доповнюючи його своїми власними деталями реалізації. Іншими словами, під час успадкування відбувається отримання нового (похідного) класу, який містить програмний код базового класу з зазначенням власних особливостей використання. Успадкування належить до типу is-a відношень між класами. При успадкуванні створюється спеціалізована версія вже існуючого класа.
Переваги використання успадкування
Правильне використанняе механізма успадкування дає наступні взаємозвязанні переваги:
- ефективна побудова важких ієрархій класів з можливістю їх модифікації. Работу класів в ієрархії можна змінювати шляхом добавлення нових успадкованих класів в потрібному місці ієрархії;
- повторне використання раніше написаного коду з подальшою його модифікацією під поставлену задачу. В свою чергу, новостворений код також може використовуватися на ієрархіях нижчих класів;
- зручність в супроводі (доповнені) програмного коду шляхом введення нових класів з новими можливостями;
- зменшення кількості логічних помилок при разробці складних програмних систем. Повторно використовуваний код частіше тестується, а, отже, менша ймовірність наявності в ньому помилок;
- легкість в узгоджені різних частин програмного коду шляхом використання інтерфейсів. Якщо два класи успадковані від загального нащадка, поведінка цих класів буде однакова у всіх випадках. Це твердження виходить з вимоги, що схожі об'єкти повинні мати схожу поведінку. Саме використання інтерфейсів зумовлює схожість поведінки об'єктів;
- створення бібліотек коду, які можна використовувати і доповнювати власними розробками;
- можливість реалізовувати відомі шаблони проектування для побудови гнучкого коду, який не змінює попередніх розробок;
- використання переваг поліморфізму неможливо без успадкування. Завдяки поліморфізму забезпечується принцип: один інтерфейс — декілька реалізацій;
- забезпечення дослідницького програмування (швидкого макетування). Таке програмування використовується у випадках, коли цілі і потреби до програмної системи на початку нечіткі. Спочатку створюється макет структури, потім цей макет поетапно вдосконалюється шляхом успадкування попереднього. Процес триває до отримання потрібного результату;
- ліпше розуміння структури програмної системи програмістом завдяки природньому представленню механізму успадкування. Якщо при побудові складних ієрархій намагатись використовувати інші принципи, то це може значно ускладнити розуміння усієї задачі і призведе до збільшення кількості помилок.
Недоліки використання успадкування
При використанні успадкування в програмах були помічені наступні недоліки:
- неможливо змінити успадковану реалізацію під час виконання;
- низька швидкість виконання. Швидкість виконання програмного коду загального призначення нижча ніж у випадку використання спеціалізованого коду, який написаний конкретно для цієї задачі. Однак, цей недолік можна виправити завдяки оптимізації коду;
- велика розмірність програм завдяки використанню бібліотек загального призначення. Якщо для деякої задачі розробляти вузькоспеціалізований програмний код, то цей код буде займати менше пам'яти ніж код загального призначення;
- збільшення складності програми у випадку неправильного або невмілого використання успадкування. Програміст зобов'язаний вміти коректно використовувати успадкування при побудові ієрархій класів. В іншому випадку це призведе до великого ускладненню програмного коду, і, як результат, збільшенню кількості помилок;
- складність засвоєння початковими програмістами основ побудови програм, які використовують успадкування. Однак, цей недолік умовний, так як залежить від досвіду програміста.
Термінологія
В об'єктно-орієнтованому програмуванні, починаючи з Simula[3] 67, абстрактні типи данних називаются класами.
Базовий клас (англ. base class) — це клас, який знаходиться на вершині ієрархії успадкування класів і в основі дерева підкласів, тобто не є підкласом і не має успадкувань від інших суперкласів або інтерфейсів. Базовим класом може бути абстрактний клас і інтерфейс. Будь-який не базовий клас є підкласом.
Суперклас (англ. super class), батьківський клас (англ. parent class), предок або надклас — клас, виконує успадкування в підкласах, тобто клас, від якого наслідуються інші класи. Суперкласом може бути підклас, базовий клас, абстрактний клас і інтерфейс.
Підклас (англ. subclass), похідний клас (англ. derived class), дочірній клас (англ. child class), клас нащадок, клас наслідник або клас-реалізатор — клас, успадкований від суперкласу або інтерфейсу, тобто клас визначений через успадкування від іншого класу або деяких таких класів. Підкласом може бути суперклас.
Інтерфейс (англ. interface) — це структура, що визначає чистий інтерфейс класу, що складається з абстрактних методів. Інтерфейси беруть участь в ієрархії успадкування класів і інтерфейсів.
Базовий інтерфейс (англ. base interface) — це аналог базового класу в ієрархії успадкування інтерфейсів, тобто це інтерфейс, який знаходиться на вершині ієрархії успадкування.
Суперінтерфейс (англ. super interface) або інтерфейс-предок — це аналог суперкласу в ієрархії успадкування, тобто це інтерфейс виконує успадкування підкласів і підінтерфейсів.
Інтерфейс-нащадок, інтерфейс-наслідник або похідний інтерфейс (англ. derived interface) — це аналог підкласу в ієрархії успадкування інтерфейсів, тобто це інтерфейс успадкований від одного або декількох суперінтерфейсів.
Ієрархія успадкування або ієрархія класів — дерево, елементами якого є класи та інтерфейси.
Застосування
Успадкування є механізмом повторного використання коду (англ. code reuse) і сприяє незалежному розширенню програмного забезпечення через відкриті класи (англ. public classes) і інтерфейси (англ. interfaces). Встановлення відношення успадкування між класами породжує ієрархію класів (англ. class hierarchy).
Типи успадкування
«Просте» успадкування
«Просте» успадкування або одиночне успадкування, описує спорідненість між двума класами: один з яких успадковується від іншого. З одного класу може виводитися багото класів, але навіть в цьому випадку подібний вид взаємозвязку залишається «простим» успадкуванням.
Абстрактні класи і створення об'єктів
Для деяких мов програмування справедлива наступна концепція.
Існують «абстрактні» класи (оголошуються такими довільно або через приписаних їм абстрактних методів); їх можна описувати наявними поля та методи. Створення ж об'єктів (екземплярів) означає конкретизацію, застосовну тільки до неабстрактних класів (в тому числі, до неабстрактних наслідникам абстрактних), — представниками яких, в результаті, будуть створені об'єкти.
Множинне успадкування
При множинному успадкуванні, у класа може бути більше одного предка. В цьому випадку клас успадковує методи всіх предків. Переваги такого підходу в більшій гнучкості.
Множинне успадкування реалізовано в C++. З інших мов, що надають цю можливість, можна відмітити Python і Eiffel. Множинне успадкування підтримується в мові UML.
Множинне успадкування — потенційне джерело помилок, які можуть виникати через наявність однакових імен методів у предків. В мовах, які позіціонуются як наслідники C++ (Java, C# та інші), від множинного успадкування було прийнято рішення відмовитись в користь інтерфейсів. Практично завжди можна обійтися без використання даного механізму. Однак, якщо така необхідність все-таки виникла, то для вирішення конфліктів використання успадкованих методів з однаковими іменами можливо, наприклад, застосувати операцію розширення видимості — «::» — для виклика конкретного метода конкретного предка.
Спроба вирішення проблеми наявності однакових імен методів в предках була предприйняти у мові Eiffel, в якій при описау нового класу необхідно явно вказати імпортовані члени кожного з успадкованих класів і їх найменування в дочірньому класі.
Більшість сучасних об'єктно-орієнтованих мов програмуння (C#, Java, Delphi та інші) підтримують можливість одночасно успадковуватись від класа-предка і реалізовувати методи декількох інтерфейсів одним і тим же класом. Цей механізм дозволяє в багато чому замінити множинне успадкування — методи інтерфейсів необхідно перевизначати явно, що виключає помилки при успадкуванні функціональності однакових методів різних класів-предків.
Єдиний базовий клас
В ряді мов програмування, усі класи, — явно або неявно, — успадковуються від деякого базового класу. Smalltalk був одним з перших мов, в яких використовувалась ця концепція. До таких мов також відносяться: Objective-C (NSObject
), Perl (UNIVERSAL
), Eiffel (ANY
), Java (java.lang.Object
), C# (System.Object
), Delphi (TObject
), Scala (Any
).
Успадкування в мовах програмування
C++
Успадкування в C++[4]:
class A {}; // Базовий клас
class B : public A {}; // Public-успадкування
class C : protected A {}; // Protected-успадкування
class Z : private A {}; // Private-успадкування
В C++ існує три типи успадкування: public, protected, private. Специфікатори доступу членів базового класу змінюються в потомках наступним чином: Якщо клас об'явлений як базовий для іншого класу з специфікатором доступу:
- public:
- public-члени базового класу — доступні як public-члени похідного класу;
- protected-члени базового класу — доступні як protected-члени похідного класу;
- protected:
- public- і protected-члени базового класу — доступні як protected-члени похідного класу;
- private:
- public- і protected-члени базового класу — доступні як private-члени похідного класу.
Одним з основних переваг public-успадкування є те, що вказівник на класи-наслідники може бути неявно преобразован у вказівник на базовий клас, тобто для приклада вище можна написати:
A* a = new B();
Ця цікава можливість відкриває можливість динамічної ідентифікації типу (RTTI).
Delphi (Object Pascal)
Для використання механізму успадкування в Delphi необхідно в оголошені класу в дужках class
вказати клас предок: Предок:
TAncestor = class
private
protected
public
//Віртуальна процедура
procedure VirtualProcedure; virtual; abstract;
procedure StaticProcedure;
end;
Наслідник:
TDescendant = class(TAncestor)
private
protected
public
//Перекриття віртуальної процедуры
procedure VirtualProcedure; override;
procedure StaticProcedure;
end;
Абсолютно всі класи в Delphi є нащадками класа TObject
. Якщо клас-предок не вказан, то мається на увазі, що новий клас є прямим нащадком класа TObject
.
Множинне успадкування в Delphi з самого початку не підтримується, однак для тих, кому без цього не обійтись все ж є такі можливості, наприклад, за рахунок використання класів-помічників(англ. Сlass Helpers).
Python
Python підтримує як одиночне, так і множинне успадкування. При доступі до атрибуту, перегляд похідних класів прохидить в порядку розширення метода (англ. method resolution order, MRO).
class Ancestor1(object): # Предок-1
def m1(self): pass
class Ancestor2(object): # Предок-2
def m1(self): pass
class Descendant(Ancestor1, Ancestor2): # Наслідник
def m2(self): pass
d = Descendant() # Ініціалізація
print d.__class__.__mro__ # Порядок розширення метода:
(<class '__main__.Descendant'>, <class '__main__.Ancestor1'>, <class '__main__.Ancestor2'>, <type 'object'>)
З версії Python 2.2[5] в мові існують «класичні» класи і «нові» класи. Останні є наслідниками object
. «Класичні» класи будуть підтримувати включно до версії 2.6, але видалені з мови в Python 3.0.
Множинне успадкування приміняється в Python для введення в основний клас класів-домішок (англ. mix-in).
PHP
Для використання механізма успадкування в PHP необхідно в оголошенні класу після імені оголошеного класу-наслідника вказати слово extends
і ім'я класу-предка:
class Descendant extends Ancestor {
}
У випадку перекриття класом-наслідником методів предка, доступ до методів предка можна отримати з використанням ключового слова parent
:
class A {
function example() {
echo "Викликаний метод A::example().<br />\n";
}
}
class B extends A {
function example() {
echo "Викликаний метод B::example().<br />\n";
parent::example();
}
}
Можна запобігти перекриття класом-наслідником методів предка; для цього необхідно вказати ключове слово final
:
class A {
final function example() {
echo "Викликаний метод A::example().<br />\n";
}
}
class B extends A {
function example() { //викличе помилку
parent::example(); //і ніколи не виконається
}
}
Щоб при успадкуванні звернутись до конструктора батьківського класу, необхідно дочірньому класу в конструкторі вказати parent::__construct();
Objective-C
@interface A : NSObject
- (void) example;
@end
@implementation
- (void) example
{
NSLog(@"Class A");
}
@end
@interface B : A
- (void) example;
@end
@implementation
- (void) example
{
NSLog(@"Class B");
}
@end
В інтерфейсі оголошують методи, які буде видно ззовні класу (public).
Внутрішні методи можна реалізовувати без інтерфейсу. Для оголошення додаткових властивостей, користуються interface-extension у файлі реалізації.
Усі методи в Objective-C віртуальні.
Java
Приклад успадкування від одного класу і двох інтерфейсів:
public class A { }
public interface I1 { }
public interface I2 { }
public class B extends A implements I1, I2 { }
Директива final
в оголошені класа робить успадкування від нього неможливим.
C#
Приклад успадкування[6] від одного класу і двох інтерфейсів:
public class A { }
public interface I1 { }
public interface I2 { }
public class B : A, I1, I2 { }
Успадкування від типізованих класів можна здійснювати, вказавши фіксований тип, або шляхом переносу змінної типу в успадкований клас:
public class A<T>
{ }
public class B : A<int>
{ }
public class B2<T> : A<T>
{ }
Допустимо також успадкування вкладених класів від класів, які їх містять:
class A // default class A is internal, not public class B can not be public (клас A за замовчуванням є внутрішнім, не публічний клас B не може бути публічним)
{
class B : A { }
}
Директива sealed
в оголошені класа робить успадкування від нього неможливим.
Ruby
class Parent
def public_method
"Public method"
end
private
def private_method
"Private method"
end
end
class Child < Parent
def public_method
"Redefined public method"
end
def call_private_method
"Ancestor's private method: " + private_method
end
end
Клас Parent
є предком для класу Child
, в якого перевизначений метод public_method
.
child = Child.new
child.public_method #=> "Redefined public method"
child.call_private_method #=> "Ancestor's private method: Private method"
Приватні методи предка можна викликати з нащадків.
JavaScript
class Parent {
constructor(data) {
this.data = data;
}
publicMethod() {
return 'Public Method';
}
}
class Child extends Parent {
getData() {
return `Data: ${this.data}`;
}
publicMethod() {
return 'Redefined public method';
}
}
const test = new Child('test');
test.getData(); // => 'Data: test'
test.publicMethod(); // => 'Redefined public method'
test.data; // => 'test'
Клас Parent
є предком для класу Child
, в якого перевизначений метод publicMethod
.
В JavaScript використовується прототипне успадкування.
Конструктори і деструктори
В С++ конструктори при успадкуванні викликаються почергово від першого предка до останнього нащадка, а деструктори навпаки — від останнього нащадка до першого предка.
class First
{
public:
First() { cout << ">>First constructor" << endl; }
~First() { cout << ">>First destructor" << endl; }
};
class Second: public First
{
public:
Second() { cout << ">Second constructor" << endl; }
~Second() { cout << ">Second destructor" << endl; }
};
class Third: public Second
{
public:
Third() { cout << "Third constructor" << endl; }
~Third() { cout << "Third destructor" << endl; }
};
// виконання коду
Third *th = new Third();
delete th;
// результат виведення
/*
>>First constructor
>Second constructor
Third constructor
Third destructor
>Second destructor
>>First destructor
*/
Див. також
Посилання
- Что такое объектно-ориентированное программирование? (Російська мова).
- Home | Computer Science and Engineering. www.cse.msu.edu. Процитовано 20 жовтня 2021.
- Ekendahl, Robert (2006). Hardware verification with C++ : a practitioner's handbook. New York: Springer. ISBN 978-0-387-36254-0. OCLC 262687433.
- C++ Inheritance.
- Python 2.2 (English).
- C# и .NET | Наследование (Російська мова).