Что такое наследование в программировании
3.4.4 Суть наследования и его польза в программировании
3.4.4 Суть наследования и его польза в программировании
3.4.4 Суть наследования и его польза в программировании
Наследование помогает программисту избежать бессмысленного написания повторяющегося кода. Как и в реальном мире, так и здесь код и данные связанные общими признаками можно записать в одном месте для всей соответствующей группы и позже на уровне специализации добавить различные дополнения и уточнения. Без этой возможности написание больших систем было бы более объёмным, и вероятность допустить ошибку была бы гораздо выше. Наследуют друг друга, как правило, классы, т.е. типы объектов. В случае наследования создаётся подкласс, который является потомком родительского класса (англ. Super class). В простейшем случае наследование может ограничиться лишь созданием подкласса. При отсутствии отдельных ограничений у созданного подкласса присутствуют все те же самые возможности, что и у родительского. В то же время самому программисту известно, в каком контексте, какой экземпляр класса он использует. Например, в случае Точки и Вектора для обоих полезно знать две координаты. В случае Точки они обозначают абсолютное местоположение, а в случае Вектора — сдвиг. В случае Вектора главным свойством является его длина, а в случае Точки она не имеет никакого значения — поэтому можно сделать вектор подклассом точки и вместе с наследованием добавить ещё соответствующую функцию длины. Цепочка наследования может иногда вырасти очень длинной — в случае множества одинаковых классов, пять-шесть уровней, где на каждом добавляются какие-то возможности и свойства — это совершенно нормально.
Наследование в программировании: определение, виды, описание и отличия
Наследование классов в ОО П ( объектно-ориентированном программировании)
- родительские или базовые классы — это классы , от которых заимствовали характеристики и свойства;
- потомственные или наследующие классы — это классы, которые получились на основе характеристик родительских классов.
- простое,
- множественное.
Простое наследование классов в программировании
Простое наследование еще называет ся одиночным — это наслед ование , при котором создается «родство» между двумя классами. То ест ь о дин класс-потомок перенимает характеристики от одного родительского класса.
Даже если от одного родительского класса будет исходить несколько классов-потомков, это все равно будет прост ым наследование м . Простое наследование поддерживается большинством объектно-ориентированных языков, в отличие от множественного наслед ования .
Множественное наслед ование классов в программировании
Множественное наслед ование классов подразумевает ситуацию, когда один потомственный класс принимает характеристики от нескольких родительских классов. Такой подход помогает реализовывать очень гибкие и настраиваемые классы-потомки.
Его поддерживают не все ОО-языки. Например, множественное наслед ование поддерживают C, C++, Python, Eiffel, UML, но не поддерживают Java и C#. Некоторые языки отказались от множественного наслед ования , потому что оно является источником потенциальных ошибок из-за присутствия одинаковых имен у классов. Поэтому в таких языках было решено уйти от множественного наслед ования и заменить его интерфейсами.
Интерфейс — это подход в программировании, при котором определяются отношения между объектами, связанны ми похожими характеристиками. Такой подход довольно популярен в объектно-ориентированном программировании, так как исключает многие ошибки, возникающие в других подходах.
Наследование в программировании: реализация в языках
Давайте посмотрим , как реализуется наследование в некоторых языках программирования.
Наследование классов в С/С++ реализуется по следующему шаблону:
class Q <>; // Родительский класс
class W : public Q <>; // спецификатор Public
class E : protected Q <>; // спецификатор Protected
class R : private Q <>; // спецификатор Private
Как видно, в С/С++ наследование может быть организовано тремя спецификаторами. Данные спецификаторы объявляются в родительском и потомственном классе. Разный спецификатор — разные отношения между этими классами. Данной теме мы посвятим отдельную статью.
Множественное наследование на Python происходит по следующему шаблону:
class FirstParent(object): # первый родительский класс
def m1(self): pass
class SecondParent(object): # второй родительский класс
def m1(self): pass
class Inheritor(FirstParent, SecondParent): # Потомственный класс с двумя родительскими
В PHP реализация наследования происходит по следующему шаблону:
class Inheritor extends Parent
>
Важная особенность в PHP — родительский класс объявляется после потомственного класса, при этом обязательно использовать перед именем родительского класса ключево е слов о «extends».
Заключение
Наследование классов в программировании — это возможность повторно использовать уже написанный код для какого-либо класса. А это существенно экономит время на разработку и отладку программы.
Наследование классов — это процесс , связанный с объектно-ориентированным программированием, однако в разных ОО-языках он реализован по-разному. Например, множественное наследование поддерживают не все объектно-ориентированные языки.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
Наследование (программирование)
Насле́дование — механизм объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.
Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.
Типы наследования
Простое наследование
Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).
В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».
Множественное наследование
Основная статья: Множественное наследование
При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.
Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.
Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.
Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.
Единый базовый класс
В ряде языков программирования все классы явно или неявно наследуются от некого базового класса. Smalltalk был одним из первых языков, в которых использовалась эта концепция. К таким языкам относятся Objective-C ( NSObject ), Perl ( UNIVERSAL ), Eiffel ( ANY ), Java ( java.lang.Object ), C# ( System.Object ), Delphi ( TObject ).
Наследование в языках программирования
Visual Basic
Наследование в Visual Basic:
Class A 'базовый класс End Class Class B : Inherits A 'наследование от A End Class Noninheritable Class C 'Класс, который нельзя наследовать (final в Java) End Class MustInherit Class Z 'Класс, который обязательно наследовать (абстрактный класс) End Class
C++
class A //базовый класс >; class B : public A //public наследование >; class C : protected A //protected наследование >; class Z : private A //private наследование >;
В C++ существует три типа наследования: public, protected, private. Спецификаторы доступа членов базового класса меняются в потомках следующим образом:
ANSI ISO IEC 14882 2003
Если класс объявлен как базовый для другого класса со спецификатором доступа public, тогда public члены базового класса доступны как public члены производного класса, protected члены базового класса доступны как protected члены производного класса.
Если класс объявлен как базовый для другого класса со спецификатором доступа protected, тогда public и protected члены базового класса доступны как protected члены производного класса.
Если класс объявлен как базовый для другого класса со спецификатором доступа private, тогда public и protected члены базового класса доступны как private члены производного класса.
\ANSI ISO IEC 14882 2003
Одним из основных преимуществ 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 ) [1] .
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().
\n"; > > class B extends A function example() echo "Вызван метод B::example().
\n"; parent::example(); > >
Objective-C
@interface MyNumber : NSObject int num; > - (int) num; - (void) setNum: (int) theNum; @end @implementation - (id) init self = [super init]; return self; > - (int) num return num; > - (void) setNum: (int) theNum num = theNum; > @end
Переопределенные методы не нужно объявлять в интерфейсе.
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 AT> > public class B : Aint> > public class B2T> : AT> >
Допустимо также наследование вложенных классов от классов, их содержащих:
class A public class B : A > >
Директива sealed в объявлении класса делает наследование от него невозможным. [2]
Ruby
class Parent def public_method "Public method" end private def private_method "Private method" end end class Children Parent def public_method "Redefined public method" end def call_private_method "Ancestor's private method: " + private_method end end
Класс Parent является предком для класса Children, у которого переопределен метод public_method.
children = Children.new children.public_method #=> "Redefined public method" children.call_private_method #=> "Ancestor's private method: Private method"
Приватные методы предка можно вызывать из наследников.
JavaScript
var Parent = function( data ) this.data = data || false; this.public_method = function() return 'Public Method'; > > var Child = function() this.public_method = function() return 'Redefined public method'; > this.getData = function() return 'Data: ' + this.data; > > Child.prototype = new Parent('test'); var Test = new Child(); Test.getData(); // => "Data: test" Test.public_method(); // => "Redefined public method" Test.data; // => "test"
Класс Parent является предком для класса Children, у которого переопределен метод public_method. В JavaScript используется прототипное наследование.
Конструкторы и деструкторы
В С++ конструкторы при наследовании вызываются последовательно от самого раннего предка до самого позднего потомка, а деструкторы наоборот — от самого позднего потомка до самого раннего предка.
class First public: First() cout <">>First constructor"; > ~First() cout <">>First destructor"; > >; class Second: public First public: Second() cout <">Second constructor"; > ~Second() cout <">Second destructor"; > >; class Third: public Second public: Third() cout <"Third constructor"; > ~Third() cout <"Third destructor"; > >; // выполнение кода Third *th = new Third(); delete th; // результат вывода /* >>First constructor >Second constructor Third constructor Third destructor >Second destructor >>First destructor */
См. также
- Виртуальное наследование
- Хрупкий базовый класс
Примечания
- ↑о порядке разрешения метода в Python
- ↑ C# Language Specification Version 4.0, Copyright © Microsoft Corporation 1999—2010
Ссылки
- Multiple Inheritance
- Проблемы множественного динамического приведения типов и их решения
Наследование в объектно-ориентированном программировании
Допустим, в программе должны быть объекты, поле number которых можно только увеличивать и уменьшать на величину шага. Также в программе нужны объекты, у которых number может изменяться не только добавлением/вычитанием шага, но также умножением на шаг. Конечно, мы можем написать еще один класс:
class NumMult(n: Int, gap: Int) { var number = n var step = gap fun inc() {number += step} fun dec() {number -= step} fun mult() {number *= step} }
Однако он во многом повторяет предыдущий класс, поэтому имеет смысл сделать его дочерним по отношению к NumInt , который выступит в роли родительского. В ООП дочерний класс наследует свойства и метода родительского. Таким образом, в NumMult нам придется описывать только дополнительную функциональность. Другими словами, дочерний наследует особенности родительского, а также расширяет, дополняет их.
Чтобы класс мог быть родительским перед его объявлением должно стоять ключевое слово open .
open class NumInc(n: Int, gap: Int) { .
В свою очередь класс-наследник должен в своем заголовке иметь запись о родительском классе. В нашем случае определение класса NumMult будет выглядеть так:
class NumMult(num: Int, coef: Int): NumInc(num, coef) { fun mult() {number *= step} }
В заголовке после параметров первичного конструктора (если он есть) ставится двоеточие, после которого идет имя родительского класса. Поскольку конструктор родительского класса предусматривает два параметра, мы должны их туда передать.
После этого объекты NumMult будут обладать теми же свойствами и методами, что и объекты NumInc . У них тоже появятся свойства number и step , методы inc() и dec() . Однако помимо этого у них есть метод mult() , которого нет у объектов родительского класса.
Наследование может быть сложнее. Дочерний класс может стать родительским по отношению к другому дочернему. Для этого перед его объявлением также должно стоять слово open .
В ряде языков программирования дочерний класс может наследовать от нескольких родительских. В Kotlin так делать нельзя, у подкласса всегда один надкласс. Проблема же множественного наследования решается через интерфейсы, которые будут рассмотрены позже.
Давайте усложним наш пример, введя в дочерний класс третье свойство.
class NumMult(num: Int, gap: Int, coef: Int): NumInc(num, gap) { var coefficient = coef fun mult() {number *= coefficient} }
Теперь дочерний класс обладает не только дополнительным методом, но и дополнительным полем. Конструктору родительского мы по-прежнему передаем два аргумента. Больше он и не принимает.
При создании объекта от класса NumMult надо передавать три аргумента:
val b = NumMult(1, 3, 2)
Первые два будут присвоены полям number и step и использоваться в функциях inc() и dec() . Третий будет присвоен свойству coefficient и использоваться только в методе mult() .
Теперь представим, что класс NumMult имеет два конструктора, а у NumInc он по прежнему один. В это случае вторичный конструктор NumMult должен делегировать к первичному своего же класса, а уже тот будет обращаться к конструктору родительского класса.
class NumMult(num: Int, gap: Int, coef: Int): NumInc(num, gap) { var coefficient = coef constructor() : this(0, 1, 2) fun mult() {number *= coefficient} }
Пример создания объекта через вторичный конструктор:
val c = NumMult()
Если у дочернего класса есть первичный конструктор, то все вторичные должны делегировать к нему. И только через него – к конструктору родительского класса. Однако если первичного конструктора нет, вторичные должны напрямую вызывать конструкторы родительского класса через ключевое слово super . Пример с двумя конструкторами как в основном, так и в дочернем классе при том, что в дочернем нет первичного:
open class NumInc(n: Int, gap: Int) { var number = n var step = gap constructor(): this(0, 1) fun inc() {number += step} fun dec() {number -= step} }
class NumMult: NumInc { var coefficient = 2 constructor(num: Int, gap: Int, coef: Int): super(num, gap) { coefficient = coef } constructor(): super() fun mult() {number *= coefficient} }
Обратите внимание, в данном случае один вторичный конструктор дочернего класса вызывает первичный родительского. Другой вторичный дочернего делегирует ко вторичному родительского. Определяется это количеством передаваемых аргументов.
Рассмотрим другое преимущество наследования в ООП. В Kotlin мы можем присвоить переменной более общего типа объект дочернего типа, а не только своего собственного.
val a: NumInc = NumInc(2, 1) val b: NumInc = NumMult(1, 3, 2)
Однако, поскольку переменная b имеет тип NumInc через нее нельзя получить доступ к свойствам и методам, которых нет в NumInc . Объект NumMult приводится к типу NumInc с потерей своих дополнительных свойств и методов.
С другой стороны, как мы узнаем из следующего урока, дочерние классы не всегда и не только отличаются от родительских расширением их возможностей, часто лишь переопределением. У дочернего класса могут быть почти такие же методы, как у родительского, но их программный код будет несколько иным.
Таким образом, объекты разных дочерних классов одного родительского, или дочернего и родительского, могут обладать одними и теми же методами. Это позволяет производить групповую обработку таких разноклассовых объектов. Например, мы можем создать список объектов NumInc и NumMult , дальше в цикле перебрать список, вызывая один и тот же метод для всех объектов.
fun main() { val a: ListNumInc> = listOf( NumMult(),NumMult(3,4,3), NumInc(10, 3), NumInc(5, 1)) for(i in a) { i.inc() println(i.number) } }
Результат выполнения программы:
1 7 13 6
Практическая работа:
Переделайте последний пример классов из урока так, чтобы родительский класс содержал только один конструктор, а дочерний – два.
X Скрыть Наверх
Введение в объектно-ориентированное программирование на Kotlin