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

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

Визначення[ред. | ред. код]

Позначення успадкування на UML-діаграмі

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

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

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

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

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

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

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

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

В об'єктно-орієнтованому програмуванні, починаючи з Simula 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++:

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 в мові існують «класичні» класи і «нові» класи. Останні являються наслідниками 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#[ред. | ред. код]

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

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
*/

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

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