Успадкування (програмування)

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
Позначення успадкування на UML-діаграмі

Успадкування (англ. inheritance) — це один з принципів об'єктно-орієнтовного програмування[1], який дає класу можливість використовувати програмний код іншого (базового) класу[2], доповнюючи його своїми власними деталями реалізації. Іншими словами, під час успадкування відбувається отримання нового (похідного) класу, який містить програмний код базового класу з зазначенням власних особливостей використання. Успадкування належить до типу is-a відношень між класами. При успадкуванні створюється спеціалізована версія вже існуючого класа.

Переваги використання успадкування[ред. | ред. код]

Правильне використанняе механізма успадкування дає наступні взаємозвязанні переваги:

  • ефективна побудова важких ієрархій класів з можливістю їх модифікації. Роботу класів в ієрархії можна змінювати шляхом добавлення нових успадкованих класів в потрібному місці ієрархії;
  • повторне використання раніше написаного коду з подальшою його модифікацією під поставлену задачу. В свою чергу, новостворений код також може використовуватися на ієрархіях нижчих класів;
  • зручність в супроводі (доповнені) програмного коду шляхом введення нових класів з новими можливостями;
  • зменшення кількості логічних помилок при разробці складних програмних систем. Повторно використовуваний код частіше тестується, а, отже, менша ймовірність наявності в ньому помилок;
  • легкість в узгоджені різних частин програмного коду шляхом використання інтерфейсів. Якщо два класи успадковані від загального нащадка, поведінка цих класів буде однакова у всіх випадках. Це твердження виходить з вимоги, що схожі об'єкти повинні мати схожу поведінку. Саме використання інтерфейсів зумовлює схожість поведінки об'єктів;
  • створення бібліотек коду, які можна використовувати і доповнювати власними розробками;
  • можливість реалізовувати відомі шаблони проектування для побудови гнучкого коду, який не змінює попередніх розробок;
  • використання переваг поліморфізму неможливо без успадкування. Завдяки поліморфізму забезпечується принцип: один інтерфейс — декілька реалізацій;
  • забезпечення дослідницького програмування (швидкого макетування). Таке програмування використовується у випадках, коли цілі і потреби до програмної системи на початку нечіткі. Спочатку створюється макет структури, потім цей макет поетапно вдосконалюється шляхом успадкування попереднього. Процес триває до отримання потрібного результату;
  • ліпше розуміння структури програмної системи програмістом завдяки природньому представленню механізму успадкування. Якщо при побудові складних ієрархій намагатись використовувати інші принципи, то це може значно ускладнити розуміння усієї задачі і призведе до збільшення кількості помилок.

Недоліки використання успадкування[ред. | ред. код]

При використанні успадкування в програмах були помічені наступні недоліки:

  • неможливо змінити успадковану реалізацію під час виконання;
  • низька швидкість виконання. Швидкість виконання програмного коду загального призначення нижча ніж у випадку використання спеціалізованого коду, який написаний конкретно для цієї задачі. Однак, цей недолік можна виправити завдяки оптимізації коду;
  • велика розмірність програм завдяки використанню бібліотек загального призначення. Якщо для деякої задачі розробляти вузькоспеціалізований програмний код, то цей код буде займати менше пам'яти ніж код загального призначення;
  • збільшення складності програми у випадку неправильного або невмілого використання успадкування. Програміст зобов'язаний вміти коректно використовувати успадкування при побудові ієрархій класів. В іншому випадку це призведе до великого ускладненню програмного коду, і, як результат, збільшенню кількості помилок;
  • складність засвоєння початковими програмістами основ побудови програм, які використовують успадкування. Однак, цей недолік умовний, так як залежить від досвіду програміста.

Термінологія[ред. | ред. код]

В об'єктно-орієнтованому програмуванні, починаючи з Simula 67[3], абстрактні типи данних називаются класами.

Базовий клас (англ. base class) — це клас, який знаходиться на вершині ієрархії успадкування класів і в основі дерева підкласів, тобто не є підкласом і не має успадкувань від інших суперкласів або інтерфейсів. Базовим класом може бути абстрактний клас і інтерфейс. Будь-який не базовий клас є підкласом.

Суперклас (англ. superclass), батьківський клас (англ. 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:
    1. public-члени базового класу — доступні як public-члени похідного класу;
    2. protected-члени базового класу — доступні як protected-члени похідного класу;
  • protected:
    1. public- і protected-члени базового класу — доступні як protected-члени похідного класу;
  • private:
    1. 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
*/

Див. також[ред. | ред. код]

Посилання[ред. | ред. код]

  1. Что такое объектно-ориентированное программирование? (Російська мова). 
  2. Home | Computer Science and Engineering. www.cse.msu.edu. Процитовано 20 жовтня 2021. 
  3. Ekendahl, Robert (2006). Hardware verification with C++ : a practitioner's handbook. New York: Springer. ISBN 978-0-387-36254-0. OCLC 262687433. 
  4. C++ Inheritance. 
  5. Python 2.2 (English). 
  6. C# и .NET | Наследование (Російська мова).