Насле́дование (англ. inheritance) — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения.
Терминология В объектно-ориентированном программировании, начиная с Simula 67, абстрактные типы данных называются классами.
Суперкласс (), родительский класс (), предок, родитель или надкласс — класс, производящий наследование в подклассах, то есть класс, от которого наследуются другие классы. Суперклассом может быть подкласс, базовый класс, абстрактный класс и интерфейс.
Подкласс (), производный класс (), дочерний класс (), класс потомок, класс наследник или класс-реализатор — класс, наследуемый от суперкласса или интерфейса, то есть класс определённый через наследование от другого класса или нескольких таких классов. Подклассом может быть суперкласс.
Базовый класс () — это класс, находящийся на вершине иерархии наследования классов и в основании дерева подклассов, то есть не являющийся подклассом и не имеющий наследований от других суперклассов или интерфейсов. Базовым классом может быть абстрактный класс и интерфейс. Любой не базовый класс является подклассом.
Интерфейс () — это структура, определяющая чистый интерфейс класса, состоящий из абстрактных методов. Интерфейсы участвуют в иерархии наследований классов и интерфейсов.
Суперинтерфейс () или интерфейс-предок — это аналог суперкласса в иерархии наследований, то есть это интерфейс производящий наследование в подклассах и подинтерфейсах.
Интерфейс-потомок, интерфейс-наследник или производный интерфейс () — это аналог подкласса в иерархии наследований интерфейсов, то есть это интерфейс наследуемый от одного или нескольких суперинтерфейсов.
Базовый интерфейс — это аналог базового класса в иерархии наследований интерфейсов, то есть это интерфейс, находящийся на вершине иерархии наследования.
Иерархия наследования или иерархия классов — дерево, элементами которого являются классы и интерфейсы.
Применение Наследование является механизмом повторного использования кода (англ. code reuse) и способствует независимому расширению программного обеспечения через открытые классы (англ. public classes) и интерфейсы (англ. interfaces). Установка отношения наследования между классами порождает иерархию классов (англ. class hierarchy).
Наследование и полиморфизм подтипов Часто наследование отождествляют с полиморфизмом подтипов (англ. subtyping): Концептуально, полиморфизм подтипов устанавливает отношение: «является» (англ. is-a relationship), — тем самым имитируя семантическое отношение наследования; В свою очередь наследование в большей степени относится к повторному использованию кода, то есть определяет синтаксическое отношение. Несмотря на приведённое выше замечание, наследование является широко используемым механизмом установки отношения «является» (англ. is-a relationship). Некоторые языки программирования согласуют наследование и полиморфизм подтипов (в основном, это относится к языкам со статической типизацией: C++, C#, Java и Scala) — в то время как другие разделяют вышеописанные концепции.
Наследование, — даже в языках программирования, которые поддерживают применение наследования как механизма, обеспечивающего полиморфизм подтипов, — не гарантирует поведенческий полиморфизм подтипов; смотри: «Принцип подстановки» Барбары Лисков.
Типы наследования
«Простое» наследование «Простое» наследование, иногда называемое одиночным наследованием, описывает родство между двумя классами: один из которых наследует второму. Из одного класса могут выводиться многие классы, но даже в этом случае подобный вид взаимосвязи остается «простым» наследованием.
Абстрактные классы и создание объектов
Для некоторых языков программирования справедлива следующая концепция.
Существуют «абстрактные» классы (объявляемые таковыми произвольно или из-за приписанных им абстрактных методов); их можно описывать имеющими поля и методы. Создание же объектов (экземпляров) означает конкретизацию, применимую лишь к неабстрактным классам (в том числе, к неабстрактным наследникам абстрактных), — представителями которых, по итогу, будут созданные объекты.
Пример: Пусть базовый класс «Сотрудник ВУЗа», — от которого наследуются классы «Аспирант» и «Профессор», — абстрактный. Общие поля и функции классов (например, поле «Год рождения») могут быть описаны в базовом классе. И в программе будут созданы объекты только производных классов: «Аспирант» и «Профессор»; обычно нет смысла создавать объекты базовых классов.
Множественное наследование
При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости.
Множественное наследование реализовано в 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: 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 поддерживает как одиночное, так и множественное наследование. При доступе к атрибуту, просмотр производных классов происходит в порядке разрешения метода ().
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, в частности, для введения в основной класс классов-примесей ().
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
class B : A { }