Как получить всех наследников класса

Обновлено: 04.07.2024

В первой статье этой серии мы рассматривали работу разных вариантов реализации перегрузки. В этой части мы сосредоточимся на таком разделе объектно-ориентированного программирования, как наследование.

Давайте сразу тезисно опишем, что такое наследование:

Рассмотрим наследование в действии

Создайте консольное приложение и назовите его InheritanceAndPolymorphism . Добавьте два класса, с названиями ClassA и ClassB , как показано ниже:

Как вы можете видеть, класс A пуст, а в B мы добавили два метода и переменную x со значением 100.

Теперь в главном методе Program.cs напишите следующее:

Разумеется, этот код вызовет ошибку:

Error: ‘InheritanceAndPolymorphism.ClassA’ does not contain a definition for ‘Display1’ and no extension method ‘Display1’ accepting a first argument of type ‘InheritanceAndPolymorphism.ClassA’ could be found (are you missing a using directive or an assembly reference?)

Очевидно, причина в том, что в классе А нет метода, который мы вызываем. Однако он есть у класса B. Было бы здорово, если бы мы могли получить доступ ко всему коду в B из A!

Теперь измените описание первого класса на следующее:

Теперь после выполнения программы мы получим:

Т.е. теперь ClassA наследует публичные методы из ClassB , это то же самое, если бы мы скопировали весь код из B в A. Всё, что объект класса B может делать, может и объект класса A. ClassA — дочерний класс, а ClassB — родительский.

Что нужно запомнить: как сын получается похожим на отца, наследует его черты, так и дочерний класс имеет параметры родительского.

ABBYY , Москва, можно удалённо , От 250 000 ₽

Теперь давайте представим, что ClassA тоже имеет метод Display1 :

Что будет, если мы запустим код теперь? Каким будет вывод? И будет ли вывод вообще или выйдет ошибка компиляции? Давайте проверим.

Однако мы также получим предупреждение:

Warning: ‘InheritanceAndPolymorphism.ClassA.Display1()’ hides inherited member ‘InheritanceAndPolymorphism.ClassB.Display1()’. Use the new keyword if hiding was intended.

Что нужно запомнить: ничто не может помешать создать в дочернем классе такой же метод, как и в родительском.

Что нужно запомнить: методы дочерних классов имеют приоритет при выполнении.

Такая возможность нам даётся для того, чтобы мы могли изменить поведение методов предка, если оно нас не устраивает. Однако мы всё равно можем вызывать методы родительского класса следующим образом:

В таком случае вывод будет:

Что нужно запомнить: ключевое слово base может быть использовано для обращения к методам класса-предка.

Что же, вверх по иерархии мы обращаться можем. Давайте попробуем сделать наоборот:

Error: ‘InheritanceAndPolymorphism.ClassB’ does not contain a definition for ‘Display2’ and no extension method ‘Display2’ accepting a first argument of type ‘InheritanceAndPolymorphism.ClassB’ could be found (are you missing a using directive or an assembly reference?)

Что нужно запомнить: наследование не работает в обратном направлении.

Когда класс A наследуется от B, он получает все его методы и может их использовать. Однако методы, которые были добавлены в A, не загружаются наверх в B, наследование не имеет обратной совместимости. Если попытаться вызвать из класса-родителя метод, который создан в классе-наследнике, вы получите ошибку.

Что нужно запомнить: кроме конструкторов и деструкторов, дочерний класс получает от родителя абсолютно всё.

Члены класса могут быть двух типов — статический, который принадлежит именно классу, или обычный, который доступен только из реализаций класса (его объектов). Чтобы сделать член статическим мы должны использовать ключевое слово static .

Если мы не наследуем класс ни от какого другого, подразумевается, что мы наследуем его от класса object . Это — родитель всех классов, и он единственный не унаследован ни от чего. Таким образом, такой код:

Таким образом, по свойству транзитивности, ClassA также является наследником object .

Теперь ещё один момент. Если мы захотим сделать так:

То у нас это не получится:

‘InheritanceAndPolymorphism.ClassW’ cannot derive from special class ‘System.ValueType’
‘InheritanceAndPolymorphism.ClassX’ cannot derive from special class ‘System.Enum’
‘InheritanceAndPolymorphism.ClassY’ cannot derive from special class ‘System.Delegate’
‘InheritanceAndPolymorphism.ClassZ’ cannot derive from special class ‘System.Array’

Что нужно запомнить: ваши классы не могут быть унаследованы от встроенных классов вроде System.ValueType , System.Enum , System.Delegate , System.Array и т.д.

Выше мы описали три класса: ClassW , ClassX и ClassY , который наследуется от первых двух. Теперь попробуем это скомпилировать:

Compile time Error: Class ‘InheritanceAndPolymorphism.ClassY’ cannot have multiple base classes: ‘InheritanceAndPolymorphism.ClassW’ and ‘ClassX’.

Если мы попробуем обойти это правило таким образом:

То это не пройдёт:

Error: Circular base class dependency involving ‘InheritanceAndPolymorphism.ClassX’ and ‘InheritanceAndPolymorphism.ClassW’.

Что нужно запомнить: классы не могут наследоваться циклически (1-й от 2-го, 2-й от 3-го 3-й от 1-го), что, в общем-то, логично.

Операции с объектами

Здесь мы пытаемся приравнять объект от разных классов друг к другу.

Cannot implicitly convert type ‘InheritanceAndPolymorphism.ClassB’ to ‘InheritanceAndPolymorphism.ClassA’

Cannot implicitly convert type ‘InheritanceAndPolymorphism.ClassA’ to ‘InheritanceAndPolymorphism.ClassB’

…мы бы продвинулсь немногим дальше:

Error: Cannot implicitly convert type ‘InheritanceAndPolymorphism.ClassB’ to ‘InheritanceAndPolymorphism.ClassA’. An explicit conversion exists (are you missing a cast?)

Что нужно запомнить: вы можете назначить переменной родительского типа объект дочернего, но не наоборот.

Здесь нам наконец-то представляется шанс обмануть правило:

Приведение типа здесь сработает, но только потому, что эти классы находятся в наследственных отношениях. Два обособленных непримитивных типа привести друг к другу нельзя.

Итак, наш последний блок кода:

Error: Cannot implicitly convert type ‘int’ to ‘char’. An explicit conversion exists (are you missing a cast?)

Что нужно запомнить: можно конвертировать char в int . Нельзя конвертировать int в char (причина в том, что диапазон целого числа больше, чем символа).

Заключение

В этой части мы рассмотрели наследование. Мы попробовали запускать разные варианты кода, чтобы возможно глубже понять суть этого принципа. *этот текст будет изменён после перевода следующей статьи* In my next article, we’ll be discussing about run time polymorphism. Inheritance plays a very important role in run time polymorphism.

Вот что вы должны были запомнить за сегодня:

Напоминаем вам, что в первой статье этой серии вы можете прочитать о полиморфизме. Продолжайте учиться программировать с нами!

Теперь, когда мы уже поговорили о наследовании в абстрактном ключе, давайте поговорим о том, как это используется в языке C++ на практике.

Наследование в С++


В диаграмме, представленной выше, Фрукт является родительским классом, а Яблоко и Банан — дочерними классами.


В этой диаграмме Треугольник является дочерним классом (родитель — Фигура ) и родительским (для Правильного треугольника ) одновременно.

Дочерний класс наследует как поведение (методы), так и свойства (переменные-члены) от родителя (с учетом некоторых ограничений доступа, которые мы рассмотрим чуть позже). Эти методы и переменные становятся членами дочернего класса.

Поскольку дочерние классы являются полноценными классами, то они могут (конечно) иметь и свои собственные члены. Сейчас мы это всё рассмотрим детально.

Класс Human

Вот простой класс Human для представления Человека:

В этом классе мы определили только те члены, которые являются общими для всех объектов этого класса. Каждый Человек (независимо от пола, профессии и т.д.) имеет Имя и Возраст.

Обратите внимание, в примере, приведенном выше, мы сделали все переменные-члены и методы класса открытыми. Это сделано ради простоты примера. Обычно переменные-члены нужно делать private. О средствах контроля доступа и о том, как это работает в наследовании, мы поговорим на соответствующих уроках.

Класс BasketballPlayer

Предположим, что нам нужно написать программу, которая будет отслеживать информацию о баскетболистах. Мы можем сохранять средний уровень игры баскетболиста и количество очков.

Вот наш незавершенный класс BasketballPlayer:

Также нам нужно знать Имя и Возраст баскетболиста, а эта информация уже у нас есть: она хранится в классе Human.

У нас есть три варианта добавления Имени и Возраста в BasketballPlayer:

Добавить Имя и Возраст в класс BasketballPlayer непосредственно в качестве членов. Это плохой вариант, так как произойдет дублирование кода, который уже существует в классе Human. Любые обновления в Human также должны быть продублированы и в BasketballPlayer.

Делаем класс BasketballPlayer дочерним

Чтобы класс BasketballPlayer унаследовал информацию от класса Human, нам нужно после объявления BasketballPlayer ( class BasketballPlayer ) использовать двоеточие, ключевое слово public и имя класса, от которого мы хотим унаследовать. Это называется открытым наследованием:


Когда BasketballPlayer наследует свойства класса Human, то BasketballPlayer приобретает методы и переменные-члены класса Human. Кроме того, BasketballPlayer имеет еще два своих собственных члена: m_gameAverage и m_points . Здесь есть смысл, так как эти свойства специфичны только для BasketballPlayer, а не для каждого Human-а.

Таким образом, объекты BasketballPlayer будут иметь 4 члена:

m_gameAverage и m_points от BasketballPlayer;

m_name и m_age от Human.

Полный код программы:

std :: cout anton . getName ( ) '\n' ; // используем метод getName(), который мы унаследовали от класса Human

Результат выполнения программы:

Это работает, так как anton является объектом класса BasketballPlayer, а все объекты класса BasketballPlayer имеют переменную-член m_name и метод getName(), унаследованные от класса Human.

Дочерний класс Employee

Работник наследует m_name и m_age от Human-а (а также два метода) и имеет еще две собственные переменные-члены и один метод. Обратите внимание, метод printNameAndWage() использует переменные как из класса, к которому принадлежит ( Employee::m_wage ), так и с родительского класса ( Human::m_name ).


Обратите внимание, классы Employee и BasketballPlayer не имеют прямых отношений, хотя оба наследуют свойства класса Human.

Результат выполнения программы:

Цепочка наследований


Все объекты Supervisor наследуют методы и переменные от Employee и Human, а также имеют свою собственную переменную-член m_nOverseesIDs .

Построив такие цепочки наследований, мы можем создать набор повторно используемых классов, которые будут иметь общие свойства вверху и становиться всё более специфичными на каждом последующем уровне наследования.

Почему наследование является полезным?

Использование наследования означает, что нам не нужно переопределять информацию из родительских классов в дочерних. Мы автоматически получаем методы и переменные-члены суперкласса через наследование, а затем просто добавляем специфичные методы или переменные-члены, которые хотим. Это не только экономит время и усилия, но также является очень эффективным: если мы когда-либо обновим или изменим базовый класс (например, добавим новые функции или исправим ошибку), то все наши производные классы автоматически унаследуют эти изменения!

Например, если мы добавим новый метод в Human, то Employee и Supervisor автоматически получат доступ к нему. Если мы добавим новую переменную в Employee, Supervisor также получит доступ к ней. Это позволяет создавать новые классы более простым, интуитивно-понятным способом!

Заключение

Наследование позволяет повторно использовать классы путем наследования членов этих классов другими классами. На следующих уроках мы будем разбираться детально, как это всё работает.

Читайте также: