Что называется наследованием в программировании
Наследование (программирование)
Насле́дование — механизм объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.
Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.
Содержание
Типы наследования
Простое наследование
Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).
В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».
Множественное наследование
При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.
Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.
Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.
Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.
BestProg
Наследование. Базовые понятия. Преимущества и недостатки. Общая форма. Простейшие примеры. Модификатор доступа protected
Содержание
Поиск на других ресурсах:
1. Что собой представляет наследование в программировании?
Наследование – это один из принципов объектно-ориентированного программирования, который дает возможность классу использовать программный код другого (базового) класса, дополняя его своими собственными деталями реализации. Иными словами, при наследовании происходит получение нового (производного) класса, который содержит программный код базового класса с указанием собственных особенностей использования. Наследование принадлежит к типу is-a отношений между классами. При наследовании создается специализированная версия уже существующего класса.
2. Преимущества использования наследования в программах
Правильное использование механизма наследования дает следующие взаимосвязанные преимущества:
3. Недостатки наследования
При использовании наследования в программах были замечены следующие недостатки:
4. Что такое базовый класс? Что такое производный класс?
Базовый класс (base class) – это класс, программный код которого используется в унаследованных (производных) классах. Производный класс (derived class) – это класс, который использует программный код базового класса и изменяет (расширяет) его под свои потребности.
В других языках программирования (например, Java) базовый класс еще называется суперкласс (superclass), а производный класс называется подкласс (subclass).
5. Синтаксис наследования в случае двух классов. Общая форма
Если один класс наследует другой базовый класс, то общая форма объявления такого класса следующая:
Например.
Все элементы базового класса, которые объявлены с модификатором private недоступны в производном классе.
Пример. В примере демонстрируется влияние модификаторов доступа на доступ к элементам базового и производного классов.
7. Сколько классов одновременно может быть унаследовано от базового класса?
Модификатор доступа protected internal объединяет ограничение модификатора protected и модификатора internal (см. пример ниже). Здесь возможны два случая:
Наследование
Наследование — это механизм создания нового класса на основе уже существующего. При этом к существующему классу могут быть добавлены новые элементы (данные и функции), либо существующие функции могут быть изменены. Основное назначение механизма наследования — повторное использование кодов, так как большинство используемых типов данных являются вариантами друг друга, и писать для каждого свой класс нецелесообразно.
Объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.
Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (иногда порождающими), а новые классы, формируемые на основе базовых, – производными (порожденными, классами-потомками или наследниками).
Производные классы «получают наследство» – данные и методы своих базовых классов, и могут пополняться собственными компонентами (данными и собственными методами). Наследуемые компоненты не перемещаются в производный класс, а остаются в базовых классах. Сообщение, обработку которого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сообщения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически в базовом классе.
При наследовании некоторые имена методов (функций-членов) и полей (данных-членов) базового класса могут быть по-новому определены в производном классе. В этом случае соответствующие компоненты базового класса становятся недоступными из производного класса. Для доступа из производного класса к компонентам базового класса, имена которых повторно определены в производном, используется операция разрешения контекста ::
Для порождения нового класса на основе существующего используется следующая общая форма
Для того, чтобы порожденный класс имел доступ к некоторым скрытым членам базового класса, в базовом классе их необходимо объявить со спецификацией доступа защищенные ( protected ).
Члены класса с доступом protected видимы в пределах класса и в любом классе, порожденном из этого класса.
Общее наследование
Спецификация доступа | внутри класса | в порожденном классе | вне класса |
private | + | — | — |
protected | + | + | — |
public | + | + | + |
Общее наследование означает, что порожденный класс – это подтип базового класса. Таким образом, порожденный класс представляет собой модификацию базового класса, которая наследует общие и защищенные члены базового класса.
Порожденный класс наследует все данные класса student (строка 13), имеет доступ к protected и public — членам базового класса. В новом классе добавлено два поля данных (строки 16, 17), и порожденный класс переопределяет функцию print() (строки 20, 39-43).
Конструктор для базового класса вызывается в списке инициализации (строка 29).
Результат выполнения
Функция main создаёт объект класса student (строка 48) и объект класса grad_student (строка 49). Указателю p присваивается ссылка на объект класса student (строка 50), а указателю gs объект класса grad_student (строка 52). При этом функции вывода в строках 51 и 54 корректно отрабатываются для своего класса.
Указатель на порожденный класс может быть неявно передан в указатель на базовый класс. А указатель на порожденный класс может указывать только на объекты порожденного класса. То есть обратное преобразование недопустимо
Неявные преобразования между порожденным и базовым классами называются предопределенными стандартными преобразованиями :
Частное наследование
Однако порождение private позволяет отдельным элементам базового класса с видимостью public и protected сохранить свою видимость в порожденном классе. Для этого необходимо
Доступ к элементам базового класса из производного класса, в зависимости от модификатора наследования:
Модификатор наследования → | public | protected | private |
Модификатор доступа ↓ | |||
public | public | protected | private |
protected | protected | protected | private |
private | нет доступа | нет доступа | нет доступа |
Конструкторы и деструкторы при наследовании
Как базовый, так и производный классы могут иметь конструкторы и деструкторы.
Вызов деструкторов при удалении этого объекта произойдет в обратном порядке:
Поскольку базовый класс «не знает» о существовании производного класса, любая инициализация выполняется в нем независимо от производного класса, и, возможно, становится основой для инициализации, выполняемой в производном классе. Поскольку базовый класс лежит в основе производного, вызов деструктора базового класса раньше деструктора производного класса привел бы к преждевременному разрушению производного класса.
Конструкторы могут иметь параметры. При реализации наследования допускается передача параметров для конструкторов производного и базового класса. Если параметрами обладает только конструктор производного класса, то аргументы передаются обычным способом. При необходимости передать аргумент из производного класса конструктору родительского класса используется расширенная запись конструктора производного класса:
Для базового и производного классов допустимо использование одних и тех же аргументов. Возможно, списки аргументов конструкторов производного и базового классов будут различны.
Конструктор производного класса не должен использовать все аргументы, часть предназначены для передачи в базовый класс (строка 29, см. код выше). В расширенной форме объявления конструктора производного класса описывается вызов конструктора базового класса.
Основные принципы ООП: инкапсуляция, наследование, полиморфизм
Contents
Абстракция [ ]
Абстра́кция — в объектно-ориентированном программировании это придание объекту характеристик, которые отличают его от всех объектов, четко определяя его концептуальные границы. Основная идея состоит в том, чтобы отделить способ использования составных объектов данных от деталей их реализации в виде более простых объектов, подобно тому, как функциональная абстракция разделяет способ использования функции и деталей её реализации в терминах более примитивных функций, таким образом, данные обрабатываются функцией высокого уровня с помощью вызова функций низкого уровня.
Такой подход является основой объектно-ориентированного программирования. Это позволяет работать с объектами, не вдаваясь в особенности их реализации. В каждом конкретном случае применяется тот или иной подход: инкапсуляция, полиморфизм или наследование. Например, при необходимости обратиться к скрытым данным объекта, следует воспользоваться инкапсуляцией, создав, так называемую, функцию доступа или свойство.
Абстракция данных — популярная и в общем неверно определяемая техника программирования. Фундаментальная идея состоит в разделении несущественных деталей реализации подпрограммы и характеристик существенных для корректного ее использования. Такое разделение может быть выражено через специальный «интерфейс», сосредотачивающий описание всех возможных применений программы [1].
С точки зрения теории множеств, процесс представляет собой организацию для группы подмножеств своего множества. См. также Закон обратного отношения между содержанием и объемом понятия.
Инкапсуляция [ ]
Инкапсуляция — свойство программирования, позволяющее пользователю не задумываться о сложности реализации используемого программного компонента (что у него внутри?), а взаимодействовать с ним посредством предоставляемого интерфейса (публичных методов и членов), а также объединить и защитить жизненно важные для компонента данные. При этом пользователю предоставляется только спецификация (интерфейс) объекта.
Пользователь может взаимодействовать с объектом только через этот интерфейс. Реализуется с помощью ключевого слова: public.
Пользователь не может использовать закрытые данные и методы. Реализуется с помощью ключевых слов: private, protected, internal.))
Инкапсуляция — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией, полиморфизмом и наследованием).
Сокрытие реализации целесообразно применять в следующих случаях:
предельная локализация изменений при необходимости таких изменений,
прогнозируемость изменений (какие изменения в коде надо сделать для заданного изменения функциональности) и прогнозируемость последствий изменений.
Наследование [ ]
Наследование — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.
Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.
Простое наследование: [ ]
Класс, от которого произошло наследование, называется базовым или родительским (англ. base class). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class).
В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».
Множественное наследование [ ]
При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.
Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.
Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.
Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.
Полиморфизм [ ]
Полиморфи́зм — возможность объектов с одинаковой спецификацией иметь различную реализацию.
Язык программирования поддерживает полиморфизм, если классы с одинаковой спецификацией могут иметь различную реализацию — например, реализация класса может быть изменена в процессе наследования[1].
Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество реализаций».
Полиморфизм — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с абстракцией, инкапсуляцией и наследованием).
Полиморфизм позволяет писать более абстрактные программы и повысить коэффициент повторного использования кода. Общие свойства объектов объединяются в систему, которую могут называть по-разному — интерфейс, класс. Общность имеет внешнее и внутреннее выражение:
Формы полиморфизма [ ]
Используя Параметрический полиморфизм можно создавать универсальные базовые типы. В случае параметрического полиморфизма, функция реализуется для всех типов одинаково и таким образом функция реализована для произвольного типа. В параметрическом полиморфизме рассматриваются параметрические методы и типы.
Параметрические метод [ ]
Если полиморфизм включения влияет на наше восприятие объекта, то параметрический полиморфизм влияет на используемые методы, так как можно создавать методы родственных классов, откладывая объявление типов до времени выполнения. Для во избежание написания отдельного метода каждого типа применяется параметрический полиморфизм, при этом тип параметров будет являться таким же параметром, как и операнды.
Параметрические типы. [ ]
Вместо того, чтобы писать класс для каждого конкретного типа следует создать типы, которые будут реализованы во время выполнения программы то есть мы создаем параметрический тип.
Наследование
1. Пегас
Предположим, что вы – волшебник и хотите создать летающую лошадь. С одной стороны, вы бы могли попробовать наколдовать пегаса. Но т.к. пегасов в природе не существует, это будет очень непросто. Придется очень многое делать самому. Куда проще взять лошадь и приколдовать ей крылья.
В программировании такой процесс называется «наследование». Предположим, вам нужно написать очень сложный класс. Писать с нуля долго, потом еще долго все тестировать и искать ошибки. Зачем идти самым сложным путем? Лучше поискать, а нет ли уже такого класса?
Предположим, вы нашли класс, который своими методами реализует 80% нужной вам функциональности. Что делать с ним дальше? Вы можете просто скопировать его код в свой класс. Но у такого решения есть несколько минусов:
Есть решение потоньше, и без необходимости получать легальный доступ к коду оригинального класса. В Java вы можете просто объявить тот класс родителем вашего класса. Это будет эквивалентно тому, что вы добавили код того класса в код своего. В вашем классе появятся все данные и все методы класса-родителя. Например, можно делать так: наследуемся от «лошади», добавляем «крылья» – получаем «пегаса»
2. Общий базовый класс
Наследование можно использовать и для других целей. Допустим, у вас есть десять классов, которые очень похожи, имеют совпадающие данные и методы. Вы можете создать специальный базовый класс, вынести эти данные (и работающие с ними методы) в этот базовый класс и объявить те десять классов его наследниками. Т.е. указать в каждом классе, что у него есть класс-родитель — данный базовый класс.
Также как преимущества абстракции раскрываются только рядом с инкапсуляцией, так и преимущества наследования гораздо сильнее при использовании полиморфизма. Но о нем вы узнаете немного позже. Сегодня же мы рассмотрим несколько примеров использования наследования.
Предположим, мы пишем программу, которая играет в шахматы с пользователем, а значит, нам понадобятся классы для фигур. Что бы это были за классы?
Очевидный ответ, если вы когда-нибудь играли в шахматы — Король, Ферзь, Слон, Конь, Ладья и Пешка.
Но в самих классах еще нужно было бы хранить информацию по каждой фигуре. Например, координаты x и y, а также ценность фигуры. Ведь некоторые фигуры ценнее других.
Кроме того, фигуры ходят по-разному, а значит и поведение классов будет отличаться. Вот как можно было бы описать их в виде классов:
Это очень примитивное описание шахматных фигур.
Общий базовый класс
Это отличный способ упростить код похожих объектов. Особенно много преимуществ мы получаем, когда в проекте тысячи различных объектов и сотни классов. Тогда правильно подобранными родительскими (базовыми) классами можно не только существенно упростить логику, но и сократить код в десятки раз.
3. Наследование класса — extends
Так что же нужно, чтобы унаследовать какой-то класс? Чтобы унаследовать один класс от другого, нужно после объявления нашего класса указать ключевое слово extends и написать имя родительского класса. Выглядит это обычно примерно так:
Именно такую конструкцию нужно написать при объявлении класса Потомок. Наследоваться, кстати, можно только от одного класса.
На картинке мы видим «корову», унаследованную от «свиньи». «Свинья» унаследована от «курицы», «курица» от «яйца». Только один родитель! Такое наследование не всегда логично. Но если есть только свинья, а очень нужна корова, программист зачастую не может устоять перед желанием сделать «корову» из «свиньи».
Хотя в Java есть множественное наследование интерфейсов. Это немного снижает остроту проблемы. Про интерфейсы мы поговорим немного позже, а пока давайте продолжим разбираться с наследованием.
Вот вам несколько историй, о том, как часто приходится делать из мухи свинью. И что за это бывает: