Наслідування (програмування): відмінності між версіями

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
[неперевірена версія][перевірена версія]
Вилучено вміст Додано вміст
Alina0510 (обговорення | внесок)
мНемає опису редагування
Alina0510 (обговорення | внесок)
Немає опису редагування
Рядок 1: Рядок 1:
{{unibox}}
{{unibox}}
[[Файл:Успадкування описане в UML-нотації.svg|міні|праворуч| Позначення успадкування на [[Unified Modeling Language|UML]]-діаграмі ]]'''Успадкування''' (англ. [[:en:Inheritance_(object-oriented_programming)#:~:text=Inheritance%20allows%20programmers%20to%20create,via%20public%20classes%20and%20interfaces.|''inheritance'']])&nbsp;— це один з принципів [[Об'єктно-орієнтоване програмування|об'єктно-орієнтовного програмування]], який дає [[Клас (програмування)|класу]] можливість використовувати програмний код іншого (базового) класу<ref>{{Cite web|title=Home {{!}} Computer Science and Engineering|url=https://www.cse.msu.edu/|website=www.cse.msu.edu|accessdate=2021-10-20}}</ref>, доповнюючи його своїми власними деталями реалізації. Іншими словами, під час успадкування відбувається отримання нового (похідного) класу, який містить програмний код базового класу з зазначенням власних особливостей використання. Успадкування належить до типу [[is-a]] відношень між класами. При успадкуванні створюється спеціалізована версія вже існуючого класа.
[[Файл:Успадкування описане в UML-нотації.svg|міні|праворуч| Позначення успадкування на [[Unified Modeling Language|UML]]-діаграмі ]]'''Успадкування''' (англ. [[:en:Inheritance_(object-oriented_programming)#:~:text=Inheritance%20allows%20programmers%20to%20create,via%20public%20classes%20and%20interfaces.|''inheritance'']]) — це один з принципів [[Об'єктно-орієнтоване програмування|об'єктно-орієнтовного програмування]]<ref>{{Cite web|url=https://ru.wh-db.com/article/chto-takoe-objecno-orientirovannoe-programmirovanie/|title=Что такое объектно-ориентированное программирование?|language=Російська мова}}</ref>, який дає [[Клас (програмування)|класу]] можливість використовувати програмний код іншого (базового) класу<ref>{{Cite web|title=Home {{!}} Computer Science and Engineering|url=https://www.cse.msu.edu/|website=www.cse.msu.edu|accessdate=2021-10-20}}</ref>, доповнюючи його своїми власними деталями реалізації. Іншими словами, під час успадкування відбувається отримання нового (похідного) класу, який містить програмний код базового класу з зазначенням власних особливостей використання. Успадкування належить до типу [[is-a]] відношень між класами. При успадкуванні створюється спеціалізована версія вже існуючого класа.


== Переваги використання успадкування ==
== Переваги використання успадкування ==
Рядок 26: Рядок 26:


== Термінологія ==
== Термінологія ==
В [[Об'єктно-орієнтоване програмування|об'єктно-орієнтованому програмуванн]]<nowiki/>і, починаючи з [[Simula|Simula 67]], абстрактні типи данних називаются класами.
В [[Об'єктно-орієнтоване програмування|об'єктно-орієнтованому програмуванн]]<nowiki/>і, починаючи з [[Simula]]<ref>{{Cite book
|url=https://www.worldcat.org/oclc/262687433
|title=Hardware verification with C++ : a practitioner's handbook
|last=Ekendahl
|first=Robert
|date=2006
|publisher=Springer
|location=New York
|isbn=978-0-387-36254-0
|oclc=262687433
}}</ref> [[Simula|67]], абстрактні типи данних називаются класами.


'''[[Базовий клас]]''' (англ. ''base class'')&nbsp;— це клас, який знаходиться на вершині ієрархії успадкування класів і в основі дерева підкласів, тобто не є підкласом і не має успадкувань від інших суперкласів або інтерфейсів. Базовим класом може бути [[абстрактний клас]] і інтерфейс. Будь-який не базовий клас являється підкласом.
'''[[Базовий клас]]''' (англ. ''base class'')&nbsp;— це клас, який знаходиться на вершині ієрархії успадкування класів і в основі дерева підкласів, тобто не є підкласом і не має успадкувань від інших суперкласів або інтерфейсів. Базовим класом може бути [[абстрактний клас]] і інтерфейс. Будь-який не базовий клас являється підкласом.
Рядок 75: Рядок 85:


=== [[C++]] ===
=== [[C++]] ===
Успадкування в C++:<syntaxhighlight lang="c++" line="1">
Успадкування в C++<ref>{{Cite web|url=https://www.cs.nmsu.edu/~rth/cs/cs177/map/inheritd.html|title=C++ Inheritance}}</ref>:<syntaxhighlight lang="c++" line="1">
class A {}; // Базовий клас
class A {}; // Базовий клас


Рядок 131: Рядок 141:
</syntaxhighlight><syntaxhighlight lang="python" line="1">
</syntaxhighlight><syntaxhighlight lang="python" line="1">
(<class '__main__.Descendant'>, <class '__main__.Ancestor1'>, <class '__main__.Ancestor2'>, <type 'object'>)
(<class '__main__.Descendant'>, <class '__main__.Ancestor1'>, <class '__main__.Ancestor2'>, <type 'object'>)
</syntaxhighlight>З версії Python 2.2 в мові існують «класичні» класи і «нові» класи. Останні являються наслідниками <code>object</code>. «Класичні» класи будуть підтримувати включно до версії 2.6, але видалені з мови в Python 3.0.
</syntaxhighlight>З версії Python 2.2<ref>{{Cite web|url=https://www.python.org/download/releases/2.2/|title=Python 2.2|language=English}}</ref> в мові існують «класичні» класи і «нові» класи. Останні являються наслідниками <code>object</code>. «Класичні» класи будуть підтримувати включно до версії 2.6, але видалені з мови в Python 3.0.


Множинне успадкування приміняється в Python для введення в основний клас класів-домішок (англ. ''mix-in'').
Множинне успадкування приміняється в Python для введення в основний клас класів-домішок (англ. ''mix-in'').
Рядок 152: Рядок 162:
}
}
}
}
</syntaxhighlight>Можна запобігти перекриття класом-наслідником методів предка; для цього необхідно вказати ключовое слово <code>final</code>:<syntaxhighlight lang="php" line="1">
</syntaxhighlight>Можна запобігти перекриття класом-наслідником методів предка; для цього необхідно вказати ключове слово <code>final</code>:<syntaxhighlight lang="php" line="1">
class A {
class A {
final function example() {
final function example() {
Рядок 204: Рядок 214:


=== [[C Sharp|C#]] ===
=== [[C Sharp|C#]] ===
Приклад успадкування від одного класу і двох інтерфейсів:<syntaxhighlight lang="c#" line="1">
Приклад успадкування<ref>{{Cite web|url=https://metanit.com/sharp/tutorial/3.7.php|title=C# и .NET {{!}} Наследование|language=Російська мова}}</ref> від одного класу і двох інтерфейсів:<syntaxhighlight lang="c#" line="1">
public class A { }
public class A { }
public interface I1 { }
public interface I1 { }

Версія за 10:26, 20 жовтня 2021

Наслідування
Схематична ілюстрація
Частково збігається з subtypingd
Позначення успадкування на UML-діаграмі

Успадкування (англ. 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++[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 необхідно в оголошені класу в дужках 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 підтримує як одиночне, так і множинне успадкування. При доступі до атрибуту, перегляд похідних класів прохидить в порядку розширення метода (англ. 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 необхідно в оголошенні класу після імені оголошеного класу-наслідника вказати слово 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();

@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 віртуальні.

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

public class A { }
        public interface I1 { }
        public interface I2 { }
        public class B extends A implements I1, I2 { }

Директива final в оголошені класа робить успадкування від нього неможливим.

Приклад успадкування[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 в оголошені класа робить успадкування від нього неможливим.

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"

Приватні методи предка можна викликати з нащадків.

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 | Наследование (Російська мова) .