Наследование в программировании: определение, виды, описание и отличия
Наследование классов в программировании свойственно объектно-ориентированным языкам, например С. Наследование входит в основные принципы ООП и стоит рядом с такими принципами, как:
абстракция,
инкапсуляция,
полиморфизм.
Абстракция — это процесс придания объекту уникальных характеристик, которые выделяют его среди других объектов, подчеркивая его концепцию и основные свойства.
Инкапсуляция — это подход в ООП, который позволяет программисту взаимодействовать с программным компонентом, не задумываясь о том, что у него внутри, а также наладить этому компоненту защиту при помощи публичных методов.
Полиморфизм — это способность объектов с одинаковой спецификой по-разному реализовываться.
Подробнее на каждом принципе мы остановимся в следующих статьях. А сегодня расскажем, что такое наследование в ООП. Наследование классов в ООП (объектно-ориентированном программировании)
Наследование в программировании — это возможность описать новый класс объекта на основе уже существующего. При таком подходе наследующий класс «перенимает» специфику родительского класса и использует ее по собственному назначению.
Другими словами, наследование помогает повторно использовать уже написанный код для определенного класса. Использование наследования образует иерархию классов, где присутствуют:
родительские или базовые классы — это классы, от которых заимствовали характеристики и свойства;
потомственные или наследующие классы — это классы, которые получились на основе характеристик родительских классов.
Наследование может быть 2-х видов:
простое,
множественное.
Простое наследование классов в программировании
Простое наследование еще называется одиночным — это наследование, при котором создается «родство» между двумя классами. То есть один класс-потомок перенимает характеристики от одного родительского класса.
Даже если от одного родительского класса будет исходить несколько классов-потомков, это все равно будет простым наследованием. Простое наследование поддерживается большинством объектно-ориентированных языков, в отличие от множественного наследования.
Множественное наследование классов в программировании
Множественное наследование классов подразумевает ситуацию, когда один потомственный класс принимает характеристики от нескольких родительских классов. Такой подход помогает реализовывать очень гибкие и настраиваемые классы-потомки.
Его поддерживают не все ОО-языки. Например, множественное наследование поддерживают 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».
Заключение
Наследование классов в программировании — это возможность повторно использовать уже написанный код для какого-либо класса. А это существенно экономит время на разработку и отладку программы.
Наследование классов — это процесс, связанный с объектно-ориентированным программированием, однако в разных ОО-языках он реализован по-разному. Например, множественное наследование поддерживают не все объектно-ориентированные языки.
Композиция или наследование: как выбрать? / Хабр
В начале…
… не было ни композиции, ни наследования, только код.
И был код неповоротливым, повторяющимся, нераздельным, несчастным, избыточным и измученным.
Основным инструментом для повторного использования кода была копипаста. Процедуры и функции были редкостью, подозрительными новомодными штучками. Вызов процедур был дорогим удовольствием. Части кода, отделенные от основной логики, вызывали недоумение!
Мрачные были времена.
Но вот лучик ООП воссиял над миром… Правда, несколько десятилетий1 никто этого не замечал. Покуда не появился графический интерфейс2, которому, как выяснилось, очень-очень не хватало ООП. Когда нажимаешь на кнопку в окне, что может быть проще, чем отправить кнопке (или ее представителю) сообщение «Нажатие»3 и получить результат?
И вот тут ООП взлетел. Было написано множество4 книг, расплодились бесчисленные5 статьи. Так что сегодня-то каждый может в объектно-ориентированное программирование, так?
Увы, код (и интернет) говорит, что не так
Самые жаркие споры и наибольшее непонимание, похоже, вызывает выбор между композицией и наследованием, зачастую выраженный мантрой «предпочитайте композицию наследованию». Вот об этом и поговорим.
Когда мантры вредят
В житейском плане «предпочитать композицию наследованию» в целом нормально, хоть я и не любитель мантр. Несмотря на то, что они зачастую и несут зерно истины, слишком легко поддаться соблазну и бездумно следовать лозунгу, не понимая, что за ним скрывается. А это всегда выходит боком.
Желтушные статьи с заголовками вроде «Наследование — зло»6 тоже не по мне, особенно если автор пытается обосновать свои набросы, сначала неправильно применяя наследование, а потом делая вывод, что оно во всем виновато. Ну типа «молотки — отстой, потому что ими нельзя завинтить шуруп.»
Начнем с основ.
Определения
Далее в статье я буду понимать под ООП «классический» объектный язык, который поддерживает классы со свойствами, методами и простое (одиночное) наследование. Никаких вам интерфейсов, примесей, аспектов, множественного наследования, делегатов, замыканий, лямбд, — ничего, кроме самых простых вещей:
- Класс: именованная сущность из предметной области, возможно, имеющая предка (суперкласс), определенная как набор полей и методов.
- Поле: именованное свойство с определенным типом, которое может, в частности, ссылаться на другой объект (см. композиция).
- Метод: именованная функция или процедура, с параметрами или без них, реализующая какое-то поведение класса.
- Наследование: класс может унаследовать — использовать по умолчанию — поля и методы своего предка. Наследование транзитивно: класс может наследоваться от другого класса, который наследуется от третьего, и так далее вплоть до базового класса (обычно —
Object
), возможно, неявного. Наследник может переопределить какие-то методы и поля чтобы изменить поведение по умолчанию. - Композиция: если поле у нас имеет тип Класс, оно может содержать ссылку на другой объект этого класса, создавая таким образом связь между двумя объектами. Не влезая в дебри различий между простой ассоциацией, агрегированием и композицией, давайте «на пальцах» определим: композиция — это когда один объект предоставляет другому свою функциональность частично или полностью.
- Инкапсуляция: мы обращаемся с объектами как с единой сущностью, а не как с набором отдельных полей и методов, тем самым скрываем и защищаем реализацию класса. Если клиентский код не знает ничего, кроме публичного интерфейса, он не может зависеть от деталей реализации.
Наследование фундаментально
Наследование — это фундаментальное понятие ООП. В языке программирования могут быть объекты и сообщения, но без наследования он не будет объектно-ориентированным (только основанным на объектах, но все еще полиморфным).
… как и композиция
Композиция это тоже фундаментальное свойство, причем любого языка. Даже если язык не поддерживает композицию (что редкость в наши дни), люди все равно будут мыслить категориями частей и компонентов. Без композиции было бы невозможно решить сложные задачи по частям.
(Инкапсуляция тоже вещь фундаментальная, но сейчас речь не о ней)
Так от чего весь сыр-бор?
Ну хорошо, и композиция, и наследование фундаментальны, в чем дело-то?
А дело в том, что можно подумать, что одно всегда может заменить другое, или что первое лучше или хуже второго. Разработка ПО — это всегда выбор разумного баланса, компромисс.
С композицией все более-менее просто, мы с ней постоянно сталкиваемся в жизни: у стула есть ножки, стена состоит из кирпичей и цемента и тому подобное. А вот наследование, несмотря на свое простое определение, может все усложнить и запутать, если хорошенько не поразмыслить над тем, как его применять. Наследование это весьма абстрактная штука, о нем можно рассуждать, но так просто его не потрогаешь. Мы, конечно, можем сымитировать наследование, используя композицию, но это, как правило, слишком много возни. Для чего нужна композиция — очевидно: из частей собрать целое. А вот с наследованием сложнее, потому что оно сразу о двух вещах: о смысле и о механике.
Наследование смысловое
Как в биологии классификация таксонов организует их в иерархии, так наследование отражает иерархию понятий из предметной области. Упорядочивает их от общего к частному, собирает родственные идеи в ветви иерархического древа. Смысл (семантика) класса по большей части выражен в его интерфейсе — наборе сообщений, которые класс способен понять, но также определяется и теми сообщениями, которыми класс отвечает. Унаследовался от предка — будь добр не только понять все сообщения, которые мог понять предок, но также и уметь ответить как он (сохранить поведение предка — прим. пер.) И поэтому наследование связывает наследника с предком гораздо сильнее, чем если бы мы взяли просто экземпляр предка как компонент. Обратите внимание, даже если класс делает что-то совсем простое, почти не имеет логики, его имя несет существенную смысловую нагрузку, разработчик делает из него важные выводы о предметной области.
Наследование механическое
Говоря о наследовании в механическом плане, мы имеем в виду, что наследование берет данные (поля) и поведение (методы) базового класса и позволяет использовать их повторно или же дополнить в наследниках. С точки зрения механики, если потомок унаследует реализацию (код) предка, то неизбежно получит и его интерфейс.
Я уверен, что в недопонимании виновата именно эта двойственная природа наследования7 в большинстве ОО-языков. Многие считают, что наследование — это чтобы повторно использовать код, хотя оно не только для этого. Если придавать повторному использованию чрезмерное значение — жди беды в архитектуре. Вот пара примеров.
Как не надо наследовать. Пример 1
class Stack extends ArrayList { public void push(Object value) { … } public Object pop() { … } }
Казалось бы, класс Stack
, все хорошо. Но посмотрите внимательно на его интерфейс. Что должно быть в классе с именем Stack? Методы push()
и pop()
, что же еще. А у нас? У нас есть get()
, set()
, add()
, remove()
, clear()
и еще куча барахла, доставшегося от ArrayList
, которое стеку ну вообще не нужно.
Можно было бы переопределить все нежелательные методы, а некоторые (например, clear()
) даже и адаптировать под наши нужды, но не многовато ли работы из-за одной ошибки в дизайне? На самом деле трех: одной смысловой, одной механической и одной комбинированной:
- Утверждение «Stack это ArrayList» ложно.
Stack
не является подтипомArrayList
. Задача стека — обеспечить выполнение правила LIFO (последним пришел, первым ушел), которое легко удовлетворяется интерфейсом push/pop, но никак не соблюдается интерфейсомArrayList
. - Механически наследование от
ArrayList
нарушает инкапсуляцию. Клиентскому коду не должно быть известно, что мы решили использоватьArrayList
для хранения элементов стека. - Ну и наконец, реализуя стек через
ArrayList
мы смешиваем две разные предметные области:ArrayList
— это коллекция с произвольным доступом, а стек — это понятие из мира очередей, со строго ограниченным (а не произвольным) 8 доступом.
Последний пункт — незначительная на первый взгляд, но важная вещь. Посмотрим на нее пристальнее.
Как не надо наследовать. Пример 2
Частая ошибка при наследовании — это создать модель из предметной области, унаследовав ее от готовой реализации. Вот, скажем, нам надо выделить некоторых наших клиентов (класс Customer
) в определенное подмножество. Легко! Наследуемся от ArrayList<Customer>
, называем это CustomerGroup
и понеслась.
Не тут-то было. Поступив так мы опять спутаем две предметные области. Старайтесь избегать этого:
ArrayList<Customer>
это уже наследник списка, утилиты типа «коллекция», готовой реализации.CustomerGroup
это совсем другая штука — класс из предметной области (домена).- Классы из предметной области должны использовать реализации, а не наследовать их.
Слой предметной области не должен знать, как у нас там все внутри сделано. Рассуждая о том, что делает наша программа, мы оперируем понятиями из предметной области, и мы не хотим отвлекаться на нюансы внутреннего устройства. Если видеть в наследовании только инструмент повторного использования кода, мы раз за разом будем попадаться в эту ловушку.
Дело не в одиночном наследовании
Одиночное наследование пока остается самой популярной моделью ООП. Оно неизбежно влечет наследование реализации, которое приводит к сильному зацеплению (coupling — прим. пер.) между классами. Может показаться, что беда в том, что ветка наследования у нас только одна на обе потребности: и смысловую и механическую. Если использовали для одного, то для другого уже нельзя. А раз так, может быть множественное наследование все исправит?
Нет. Отношение наследования не должно пересекать границы между предметными областями: инструментальной (структуры данных, алгоритмы, сети) и прикладной (бизнес-логика). Если CustomerGroup будет наследовать ArrayList<Customer>
и одновременно, скажем, DemographicSegment, то две предметные области переплетутся между собой, а «видовая принадлежность» объектов станет неочевидна.
Предпочтительно (по крайней мере, с моей точки зрения) делать так. Наследуемся от имеющихся в языке инструментальных классов по минимуму, ровно настолько, чтобы реализовать «механическую» часть вашей логики. Потом соединяем получившиеся части композицией, но не наследованием. Иными словами:
От инструментов можно наследовать только другие инструменты.
Это очень частая ошибка новичков. Что не удивительно, ведь так просто взять и унаследовать. Редко где встретишь обсуждения, почему именно это неправильно. Еще раз: бизнес-сущности должны пользоваться инструментами, а не быть ими. Мухи (инструменты) — отдельно, котлеты (бизнес-модели) — отдельно.
Так когда же нужно наследование?
Наследуемся как надо
Чаще всего — и при этом с наибольшей отдачей — наследование применяют для описания объектов, незначительно отличающихся друг от друга (в оригинале используется термин «differential programming» — прим. пер.) Например, нам нужна особенная кнопка с небольшими дополнениями. Нормально, наследуемся от существующего класса Кнопка. Потому что наш новый класс, это все еще кнопка, а мы полностью наследуем API класса Кнопка, его поведение и реализацию. Новая функциональность только добавляется к существующему. А вот если в наследнике часть функциональности убирается, это повод задуматься, а нужно ли наследование.
Наследование полезнее всего для группировки сходных сущностей и понятий, определения семейств классов, и вообще для организации терминов и понятий, описывающих предметную область. Зачастую, когда значительная часть предметной логики уже реализована, исходно выбранные иерархии наследования перестают работать. Если всё к тому идет, не бойтесь разобрать и заново сложить эти иерархии9 так, чтобы они лучше соответствовали и работали друг с другом.
Композиция или наследование: что выбрать?
В ситуации, когда вроде бы подходит и то и другое, взгляните на дизайн в двух плоскостях:
- Структура и механическое исполнение бизнес-объектов.
- Что они обозначают по смыслу и как взаимодействуют.
Пока наследование остается внутри одной плоскости, все нормально. Но если иерархия проходит через две плоскости сразу, это плохой симптом.
Например, у вас есть один объект внутри другого. Внутренний объект реализует значительную часть поведения внешнего. У внешнего объекта куча прокси-методов, которые тупо пробрасывают параметры во внутренний объект и возвращают от него результат. В этом случае посмотрите, а не стоит ли унаследоваться от внутреннего объекта, хотя бы частично.
Разумеется, никакие инструкции не заменят голову на плечах. Когда строишь объектную модель, вообще полезно думать. Но если вам хочется конкретных правил, то пожалуйста.
Наследуем, если:
- Оба класса из одной предметной области
- Наследник является корректным подтипом (в терминах LSP — прим. пер.) предка
- Код предка необходим либо хорошо подходит для наследника
- Наследник в основном добавляет логику
Иногда все эти условия выполняются одновременно:
- в случае моделирования высокоуровневой логики из предметной области
- при разработке библиотек и расширений для них
- при дифференциальном программировании (автор снова использует термин «differential programming», очевидно, понимая под ним нечто, отличное от DDP — прим. пер.)
Если это не ваш случай, то и наследование вам, скорее всего, будет нужно не часто. Но не потому, что надо «предпочитать» композицию наследованию, и не потому что она «лучше». Выбирайте то, что подходит наилучшим образом для конкретно вашей задачи.
Надеюсь, эти правила помогут вам понять разницу между двумя подходами.
Приятного кодинга!
Послесловие
Отдельная благодарность сотрудникам ThoughtWorks за их ценный вклад и замечания: Питу Хогсону, Тиму Брауну, Скотту Робинсону, Мартину Фаулеру, Минди Ор, Шону Ньюхэму, Сэму Гибсону и Махендре Кария.
1
Первый официальный ОО-язык, SIMULA 67, появился в 1967 году.
2
Системные и прикладные программисты приняли на вооружение C++ в середине 1980-х, но перед тем, как ООП стал общепринятым, прошел еще десяток лет.
3
Я намеренно упрощаю, не говорю про паб/саб, делегатов и тому подобное, чтобы не раздувать статью.
4
На момент написание этого текста Амазон предлагает 24777 книг по ООП.
5
Поиск в гугле по фразе «объектно-ориентированное программирование» дает 8 млн результатов.
6
Поиск в гугле выдает 37600 результатов по запросу «наследование это зло».
7
Смысл (интерфейс) и механику (исполнение) можно разделить за счет усложнения языка. См. пример из спецификации языка D.
8
С грустью замечу, что в Java Stack
унаследован от Vector
.
9
Проектирование для повторного использования через наследования выходит за рамки темы статьи. Просто имейте в виду, что ваш дизайн должен удовлетворить потребности и тех, кто пользуется базовым классом, и тех, кому нужен наследник.
Переводчик выражает благодарность ООП-чату в Telegram, без которого этот текст не смог бы появиться.
Наследование в объектно-ориентированном программировании
Химанши Сингх — Опубликовано 26 октября 2020 г. и изменено 14 июня 2022 г.
Новичок Программирование Питон Техника
Обзор
- Узнайте о наследовании в объектно-ориентированном программировании и различных формах наследования
- Понимание переопределения методов и функции super() в мире объектно-ориентированного программирования
Введение
Наследование — один из наиболее важных аспектов объектно-ориентированного программирования (ООП). Ключ к пониманию наследования заключается в том, что оно обеспечивает возможность повторного использования кода. Вместо написания одного и того же кода снова и снова мы можем просто наследовать свойства одного класса в другой.
Как вы понимаете, это экономит массу времени. А в науке о данных время — деньги!
ООП — это объекты реального мира, а наследование — это способ представления реальных отношений. Вот пример — автомобиль, автобус, велосипед — все они относятся к более широкой категории под названием Транспортное средство . Это означает, что они унаследовали свойства транспортных средств класса, т.е. все они используются для перевозки.
Мы можем представить эту связь в коде с помощью наследования.
Еще одна интригующая особенность наследования заключается в том, что оно носит транзитивный характер. Но что это значит? Мы увидим подробно позже в этой статье. Python также поддерживает различные типы наследования, о которых я подробно расскажу в этой статье.
Это вторая статья из серии статей, посвященных объектно-ориентированному программированию. Ознакомьтесь также с первой статьей:
- Основные концепции объектно-ориентированного программирования.
Содержание
- Что такое наследование в объектно-ориентированном программировании?
- Различные формы наследования в объектно-ориентированном программировании
- Одиночное наследство
- Множественное наследование
- Многоуровневое наследование
- Иерархическое наследование
- Гибридное наследие
- Переопределение метода
- Функция super()
Что такое наследование в объектно-ориентированном программировании?
Наследование — это процедура, при которой один класс наследует атрибуты и методы другого класса. Класс, свойства и методы которого наследуются, называется родительским классом. И класс, который наследует свойства от родительского класса, является дочерним классом.
Интересно, что наряду с унаследованными свойствами и методами у дочернего класса могут быть свои собственные свойства и методы.
Вы можете использовать следующий синтаксис:\ для реализации наследования в Python:
класс parent_class: тело родительского класса класс дочерний_класс (родительский_класс): тело дочернего класса
Давайте посмотрим на реализацию:
Код Python:
Мы создали два дочерних класса, а именно «BMW» и «Audi», которые унаследовали методы и свойства родительского класса «Car». Мы не предоставили никаких дополнительных функций и методов в классе BMW. В то время как внутри класса Audi есть еще один метод.
Обратите внимание, что метод экземпляра description() родительского класса доступен объектам дочерних классов с помощью obj1.description() и obj2.description(). И отдельный метод класса Audi также доступен с помощью obj2.audi_desc().
Мы можем проверить базовый или родительский класс любого класса, используя встроенный атрибут класса __bases__
печать(BMW. __базы__, Audi.__базы__)
Как мы видим, базовым классом обоих подклассов является Автомобиль. Теперь давайте посмотрим, что происходит при использовании __base__ с родительским классом Car:
печать(автомобиль.__базы__)
Всякий раз, когда мы создаем новый класс в Python 3.x, он наследуется от встроенного базового класса с именем Object. Другими словами, класс Object является корнем всех классов.
Формы наследования в объектно-ориентированном программировании
В целом существует пять форм наследования, основанных на участии родительского и дочернего классов.
1. Одиночное наследование
Это форма наследования, при которой класс наследует только один родительский класс. Это простая форма наследования, поэтому ее также называют простым наследованием .
класс Родитель: защита f1(я): print("Функция родительского класса.") класс Ребенок (Родитель): защита f2(я): print("Функция дочернего класса") объект1 = Ребенок() объект1. f1() объект1.f2()
Здесь класс Child наследует только один класс Parent, следовательно, это пример одиночного наследования.
2. Множественное наследование
Наследование становится множественным наследованием, когда класс наследует более одного родительского класса. Дочерний класс после наследования свойств от различных родительских классов имеет доступ ко всем их объектам.
класс Parent_1: защита f1(я): print("Функция класса parent_1.") класс Parent_2: защита f2(я): print("Функция класса parent_2.") класс Parent_3: защита f3(я): print("функция класса parent_3.") класс Ребенок (Родитель_1, Родитель_2, Родитель_3): защита f4(я): print("Функция дочернего класса") object_1 = Ребенок() объект_1.f1() объект_1.f2() объект_1.f3() объект_1.f4()
Здесь у нас есть один дочерний класс, который наследует свойства трех родительских классов Parent_1, Parent_2 и Parent_3. Все классы имеют разные функции, и все функции вызываются с использованием объекта класса Child.
Но предположим, что дочерний класс наследует два класса, выполняющих одну и ту же функцию:
класс Parent_1: защита f1(я): print("Функция класса parent_1.") класс Parent_2: защита f1(я): print("Функция класса parent_2.") класс Ребенок (Родитель_1, Родитель_2): защита f2(я): print("Функция дочернего класса")
Здесь классы Parent_1 и Parent_2 имеют одинаковую функцию f1(). Теперь, когда объект класса Child вызывает f1(), поскольку класс Child наследует оба родительских класса, что, по вашему мнению, должно произойти?
объект = Ребенок() объект.f1()
Но почему не унаследована функция f1() класса Parent_2?
При множественном наследовании дочерний класс сначала ищет метод в своем собственном классе. Если не найдено, то ищет в родительских классах depth_first и в порядке слева-направо. Поскольку это был простой пример всего с двумя родительскими классами, мы ясно видим, что класс Parent_1 был унаследован первым, поэтому дочерний класс будет искать метод в классе Parent_1 перед поиском в классе Parent_2.
Но для сложных проблем наследования определить порядок становится сложно. Таким образом, фактический способ сделать это называется Порядок разрешения методов (MRO) в Python. Мы можем найти MRO любого класса, используя атрибут __mro__
Ребенок.__мро__
Это говорит о том, что класс Child сначала посетил класс Parent_1, а затем Parent_2, поэтому будет вызван метод f1() класса Parent_1.
Возьмем немного сложный пример на Python:
класс Parent_1: проходить класс Parent_2: проходить класс Parent_3: проходить класс Child_1 (Parent_1,Parent_2): проходить класс Child_2 (Parent_2,Parent_3): проходить класс Child_3(Child_1,Child_2,Parent_3): пройти
Здесь класс Child_1 наследует два класса — Parent_1 и Parent_2. Класс Child_2 также наследует два класса — Parent_2 и Parent_3. Другой класс Child_3 наследует три класса — Child_1, Child_2 и Parent_3.
Теперь, просто взглянув на это наследование, довольно сложно определить порядок разрешения методов для класса Child_3. Итак, вот фактическое использование __mro__-
Ребенок_3.__мро__
Мы видим, что сначала интерпретатор ищет Child_3, затем Child_1, затем Parent_1, Child_2, Parent_2 и Parent_3 соответственно.
3. Многоуровневое наследование
Например, класс_1 наследуется классом_2, а этот класс_2 также наследуется классом_3, и этот процесс продолжается. Это известно как многоуровневое наследование. Давайте разберемся на примере:
Родительский класс: защита f1(я): print("Функция родительского класса.") класс Child_1 (родитель): защита f2(я): print("Функция класса child_1.") класс Child_2(Child_1): защита f3(я): print("Функция класса child_2.") obj_1 = Ребенок_1() obj_2 = Ребенок_2() obj_1.f1() obj_1.f2() печать("\n") obj_2.f1() obj_2.f2() obj_2.f3()
Здесь класс Child_1 наследует класс Parent, а класс Child_2 наследует класс Child_1. При этом Child_1 имеет доступ к функциям f1() и f2(), тогда как Child_2 имеет доступ к функциям f1(), f2() и f3(). Если мы попытаемся обратиться к функции f3() с помощью объекта класса Class_1, то возникнет ошибка следующего содержания:
Объект «Ребенок_1» не имеет атрибута «f3»
obj_1.f3()
4- Иерархическое наследование
При этом различные дочерние классы наследуют один родительский класс. Пример, приведенный во введении к наследованию, является примером иерархического наследования, поскольку классы BMW и Audi наследуют класс Car.
Для простоты рассмотрим другой пример:
Родительский класс: deff1(я): print("Функция родительского класса.") класс Child_1 (родитель): deff2(я): print("Функция класса child_1.") класс Child_2 (родитель): deff3(я): print("Функция класса child_2.") obj_1 = Ребенок_1() obj_2 = Ребенок_2() obj_1.f1() obj_1.f2() печать('\п') obj_2.f1() obj_2.f3()
Здесь два дочерних класса наследуют один и тот же родительский класс. Класс Child_1 имеет доступ к функциям f1() класса Parent и функции f2() самого себя. Тогда как класс Child_2 имеет доступ к функциям f1() класса Parent и функции f3() самого себя.
5- Гибридное наследование
Сочетание нескольких форм наследования называется гибридным наследованием. После этого примера будет понятнее:
Родительский класс: защита f1(я): print("Функция родительского класса.") класс Child_1 (родитель): защита f2(я): print("Функция класса child_1.") класс Child_2 (родитель): защита f3(я): print("Функция класса child_2.") класс Child_3 (Child_1, Child_2): защита f4(я): print("Функция класса child_3.") объект = Ребенок_3() объект.f1() объект.f2() объект.f3() объект.f4()
В этом примере два класса «Дочерний_1» и «Дочерний_2» являются производными от базового класса «Родительский» с использованием иерархического наследования. Другой класс «Child_3» является производным от классов «Child_1» и «Child_2» с использованием множественного наследования. Класс «Child_3» теперь получен с использованием гибридного наследования.
Переопределение метода
Концепция переопределения очень важна в наследовании. Это дает особую возможность дочерним/подклассам предоставлять конкретную реализацию метода, который уже присутствует в их родительских классах.
Родительский класс: защита f1(я): print("Функция родительского класса") класс Ребенок (Родитель): защита f1(я): print("Функция дочернего класса.") объект = ребенок () obj.f1()
Здесь функция f1() класса Child переопределяет функцию f1() класса Parent. Всякий раз, когда объект класса Child вызывает f1(), выполняется функция класса Child. Однако объект родительского класса может вызывать функцию f1() родительского класса.
obj_2 = Родительский() obj_2.f1()
Функция super()
Функция super() в Python возвращает прокси-объект, который ссылается на родительский класс, используя ключевое слово super . Это ключевое слово super() в основном полезно для доступа к переопределенным методам родительского класса.
Официальная документация по функции super() содержит два основных варианта использования super():
- В иерархии классов с одиночным наследованием, super помогает ссылаться на родительские классы, не называя их явно, что делает код более удобным для сопровождения.
Пример-
Родительский класс: защита f1(я): print("Функция родительского класса") класс Ребенок (Родитель): защита f1(я): супер().f1() print("Функция дочернего класса.") объект = ребенок () obj.f1()
Здесь с помощью super().f1() был вызван метод f1() суперкласса дочернего класса, т.е. родительского класса, без явного указания его имени.
Следует отметить, что класс super() может принимать два параметра: первый — это имя подкласса, а второй — объект, являющийся экземпляром этого подкласса. Посмотрим как-
Родительский класс: защита f1(я): print("Функция родительского класса") класс Ребенок (Родитель): защита f1(я): супер( Ребенок, я ). f1() print("Функция дочернего класса.") объект = ребенок () obj.f1()
Первый параметр относится к подклассу Child , а второй параметр относится к объекту Child, который в данном случае равен self . Вы можете видеть, что результат после использования super() и super(Child, self) одинаков, потому что в Python 3 super(Child, self) эквивалентен self().
Теперь давайте рассмотрим еще один пример использования функции __init__.
класс Родительский (объект): def__init__(я, имя_родителя): print(ParentName, 'производный от другого класса.') класс Ребенок (Родитель): def__init__(я, ChildName): print(name,'является подклассом.') super().__init__(Имя_ребенка) obj = Ребенок('Ребенок')
Здесь мы сделали то, что вызвали функцию __init__ класса Parent (внутри класса Child), используя super().__init__( ChildName ) . И поскольку метод __init__ класса Parent требует один аргумент, он был передан как «ChildName». Итак, после создания объекта класса Child сначала выполняется функция __init__ класса Child, а затем функция __init__ класса Parent.
- Второй вариант использования — поддержка совместного множественного наследования в динамической среде выполнения.
класс Первый(): защита __init__(сам): распечатать("первый") супер().__инит__() класс второй(): защита __init__(сам): распечатать("секунда") супер().__инит__() класс Третий (Второй, Первый): защита __init__(сам): распечатать("третий") супер().__инит__() obj = Third()
Вызов super() находит следующий метод в MRO на каждом шаге, поэтому First и Second тоже должны иметь его, иначе выполнение останавливается в конце первый().__init__ .Обратите внимание, что суперклассом Первого и Второго является Объект .
Давайте также найдем MRO для Third()-
Third.__mro__
Порядок: Третий > Второй > Первый, и такой же порядок вывода.
End Notes
В заключение, в этой статье я развил концепцию наследования в объектно-ориентированном программировании на Python. Я рассмотрел различные формы наследования и некоторые общие концепции наследования, такие как переопределение метода и функция super().
Надеюсь, вы поняли принципы, изложенные в этой статье. Дайте мне знать в комментариях ниже, если у вас есть какие-либо вопросы.
переопределение метода наследования Объектно-ориентированное программированиефункция super()
Содержание
Шайвья Вашиштха говорит:
Отличная работа!! было действительно интересно и полезноВайбхав Караят говорит:
Довольно грамотный блог. Спасибо.Химанши Сингх говорит:
Рад помочь!Химанши Сингх говорит:
Рад помочь!Лучшие ресурсы
Скачать приложение
Мы используем файлы cookie на веб-сайтах Analytics Vidhya для предоставления наших услуг, анализа веб-трафика и улучшения вашего опыта на сайте. Используя Analytics Vidhya, вы соглашаетесь с нашей Политикой конфиденциальности и Условиями использования. Примите
Политику конфиденциальности и использования файлов cookie
Станьте полноценным специалистом по данным
Продвиньтесь вперед в своей карьере AI ML | Предварительные условия не требуютсяСкачать брошюруЧто такое наследование в программировании? Итак, наследование определяется как тенденция одного класса получать свойства и характеристики от других классов. Он предоставляет дополнительные функциональные возможности для извлечения функций из базового класса и значительного включения их в другие производные классы.
В компьютерном программировании наследование является важной концепцией парадигмы объектно-ориентированного программирования (ООП). Он предоставляет механизм для установления отношений и построения иерархий классов в составе объектов. Наследование означает использование предварительно написанного или созданного ранее кода. И нужно помнить одну вещь: мы просто используем код, а не обновляем или изменяем его. Функции и методы, определенные в одном классе, могут использоваться для управления другими элементами данных этого класса.
Понимание наследования
Чтобы понять наследование, нам нужно сосредоточиться на двух типах классов: подклассе и суперклассе .
Подкласс , , также известный как производный класс, это класс, который наследует свойство, а суперкласс также известен как базовый класс; это класс, от которого наследуются свойства. В некоторых случаях подкласс называется дочерним классом, а суперкласс известен как родительский класс. Итак, до сих пор очевидно, что подкласс, то есть дочерний класс, наследует свойства суперкласса, то есть родительского класса.
Как наследование делает работу такой легкой?
В объектно-ориентированном программировании всякий раз, когда создается класс, назначаются элементы данных и функции данных. Члены данных — это те переменные, которые объявлены в одном и том же классе. Функции данных — это методы или функции, определенные в классе. Эти функции обычно используются для управления элементами данных. Эти функции данных и элементы данных могут быть общедоступными или частными, в зависимости от области использования.
Итак, с помощью наследования нам не нужно рекурсивно создавать и определять элементы данных и функции. Мы кодируем один раз в классе, и они могут наследовать все свойства элементов данных и функций в последующем подклассе. Эта функция также помогает в эффективном динамическом программировании.
Типы наследования:
- Одиночное наследование: Один производный класс наследуется от одного базового класса.
- Множественное наследование: Один производный класс наследуется от многих базовых классов.
- Многоуровневое наследование: Один производный класс наследуется от других производных классов.
- Иерархальное наследование: Более одного производного класса наследуются от одного базового класса.
- Наследование гибридов: Сочетание более чем одного типа наследования.
Что можно сделать с наследством?
Если команда работает над большой программой или фрагментом кода, наследование действует как благословение. Нам не нужно писать код повторно; если есть иерархия в классах или классах, которые зависят друг от друга, мы можем использовать код наследования от базового класса к дочернему классу. Это предотвратит повторное написание одного и того же кода. Наследование также играет важную роль в динамическом программировании. Один код можно использовать во многих областях, и, следовательно, сложность может быть уменьшена. Если кто-то внесет изменения в один подкласс, это не повлияет на суперкласс или другие классы.
Преимущества наследования
Ниже перечислены некоторые плюсы наследования:
- Частое использование кода, написанного один раз, т. е. повторное использование кода.
- Один суперкласс может использоваться для количества подклассов в иерархии.
- Никаких изменений во всех базовых классах; просто делайте изменения только в родительском классе.
- Наследование используется для создания более доминирующих объектов.
- Наследование позволяет избежать дублирования и избыточности данных.
- Наследование используется, чтобы избежать пространственной и временной сложности.
Почему наследование важно в программировании?
Наследование важно в программировании из-за возможности повторного использования кода. Мы можем избежать дублирования данных и избыточности в нашей программе. Возьмем пример: если вы живете с родителями, то отец будет одним базовым классом, а вы (как ребенок) будете производным классом. Таким образом, мы можем наследовать многие вещи, такие как фамилия, адресная строка, город и штат от родительского класса. Кроме того, если мы хотим обновить адрес или что-то еще, мы просто изменим адрес базового класса, и все производные классы унаследуют свойство этого базового класса.
Почему мы должны использовать наследование?
В первую очередь мы используем наследование для использования определенных частей кода и изменения определенных функций в соответствии с нашими потребностями, и это можно сделать без каких-либо сложностей. Наследование обеспечивает гибкость нашего кода для его повторного использования из базового класса в требуемый производный класс. Дочерний класс может переопределить свойства базового класса, не переписывая код в одном и том же классе снова и снова.
Зачем нам наследство?
- Чтобы повторно использовать код, напишите код и применяйте его дальше, где это необходимо.
- Во избежание дублирования и избыточности данных в программе.
- Чтобы уменьшить пространственную и временную сложность.
- Проще в парадигме иерархического программирования.
- Переменные с одним и тем же именем могут использоваться несколько раз в области действия кода.
- Для создания доминирующих объектов данных и функций.
Кто является подходящей аудиторией для изучения технологий наследования?
- Студенты факультета компьютерных наук.
- Студенты, изучающие программирование.
- Программист среднего уровня.
- выпускников информационных технологий.
- выпускников факультета электроники.
- Технический специалист, который занимается или хочет работать над иерархическим программированием.
Как эта технология поможет вам в карьерном росте?
Судя по обсуждению, технология наследования является важной частью объектно-ориентированного программирования, поэтому любой, кто хочет улучшить объектно-ориентированное программирование, должен иметь опыт работы с технологией наследования. Кроме того, если человек обладает обширными знаниями в области технологии наследования, он может обеспечить стабильность в секторе разработки кода компании и может добиться признания или хорошей позиции в команде. Это также помогает организации получать больший доход, а также может работать хорошо.