Interested Article - Наследование (программирование)
- 2021-10-07
- 1
Наследование (англ. ) — концепция объектно-ориентированного программирования , согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения .
Терминология
В объектно-ориентированном программировании , начиная с Simula 67 , абстрактные типы данных называются классами .
Суперкласс ( англ. super class ), родительский класс ( англ. parent class ), предок, родитель или надкласс — класс, производящий наследование в подклассах, т. е. класс, от которого наследуются другие классы. Суперклассом может быть подкласс, базовый класс, абстрактный класс и интерфейс.
Подкласс ( англ. subclass ), производный класс ( англ. derived class ), дочерний класс ( англ. child class ), класс потомок, класс наследник или класс-реализатор — класс, наследуемый от суперкласса или интерфейса, т. е. класс определённый через наследование от другого класса или нескольких таких классов. Подклассом может быть суперкласс.
Базовый класс ( англ. base class ) — это класс, находящийся на вершине иерархии наследования классов и в основании дерева подклассов, т. е. не являющийся подклассом и не имеющий наследований от других суперклассов или интерфейсов. Базовым классом может быть абстрактный класс и интерфейс. Любой не базовый класс является подклассом.
Интерфейс ( англ. interface ) — это структура, определяющая чистый интерфейс класса, состоящий из абстрактных методов. Интерфейсы участвуют в иерархии наследований классов и интерфейсов.
Суперинтерфейс ( англ. super interface ) или интерфейс-предок — это аналог суперкласса в иерархии наследований, т. е. это интерфейс производящий наследование в подклассах и подинтерфейсах.
Интерфейс-потомок, интерфейс-наследник или производный интерфейс ( англ. derived interface ) — это аналог подкласса в иерархии наследований интерфейсов, т. е. это интерфейс наследуемый от одного или нескольких суперинтерфейсов.
Базовый интерфейс — это аналог базового класса в иерархии наследований интерфейсов, т. е. это интерфейс, находящийся на вершине иерархии наследования.
Иерархия наследования или иерархия классов — дерево, элементами которого являются классы и интерфейсы.
Применение
Наследование является механизмом повторного использования кода (англ. code reuse ) и способствует независимому расширению программного обеспечения через открытые классы (англ. public classes) и интерфейсы (англ. interfaces). Установка отношения наследования между классами порождает иерархию классов (англ. class hierarchy).
Наследование и полиморфизм подтипов
Часто наследование отождествляют с полиморфизмом подтипов (англ. subtyping):
- Концептуально, полиморфизм подтипов устанавливает отношение: «является» (англ. ), — тем самым имитируя семантическое отношение наследования;
- В свою очередь наследование в большей степени относится к повторному использованию кода , то есть определяет синтаксическое отношение.
Несмотря на приведённое выше замечание, наследование является широко используемым механизмом установки отношения «является» (англ. 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
:
Одним из основных преимуществ 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
.
Множественное наследование в 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
{
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 используется прототипное наследование.
Конструкторы и деструкторы
В C++ конструкторы при наследовании вызываются последовательно от самого раннего предка до самого позднего потомка, а деструкторы наоборот — от самого позднего потомка до самого раннего предка.
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
*/
Ссылки
[cplus.about.com/od/beginnerctutorial/l/aa121302a.htm Multiple Inheritance]
Примечания
- . Дата обращения: 1 февраля 2007. 29 марта 2012 года.
- . wh-db.com (30 июня 2015). Дата обращения: 1 июля 2015. 1 июля 2015 года.
- C# Language Specification Version 4.0, Copyright © Microsoft Corporation 1999—2010
|
Некоторые
внешние ссылки
в этой статье
ведут на сайты, занесённые в
спам-лист
|
- 2021-10-07
- 1