Множественное наследование примеры

Множественное наследование

Один класс может наследовать атрибуты двух и более классов одновременно. Для этого использу­ется список базовых классов, в котором каждый из базовых классов отделен от других запятой. Общая форма множественного наследования имеет вид:

class имя_порожденного_класса: список базовых классов
<
.
>;

В следующем примере класс Z наследует оба класса X и Y:

Множественное наследование примеры

2.1.10. Множественное наследование

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

Пример множественного наследования приведен на рисунке 2.20, на котором рассмотрена классификация транспортных средств. Класс транспортное средство имеет два подкласса сухопутное_ТС и водное_ТС (зачерненный треугольник, используемый для обозначения наследования, означает, что подклассы имеют непустое пересечение). Класс амфибии имеет два суперкласса сухопутное_ТС и водное_ТС, наследуя все свойства (атрибуты и операции) как класса сухопутное_ТС, так и класса водное_ТС.

Рис. 2.20. Множественное наследование

Еще один пример множественного наследования приведен на рисунке 2.21, где рассмотрено множественное наследование от непересекающихся классов. В этом случае, который наиболее типичен для применения множественного наследования, свойства, унаследованные от разных предков, дополняют друг друга.

Рис. 2.21. Множественное наследование от непересекающихся классов

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

Использование вложенного простого наследования представлено на рисунке 2.22.

Делегирование с использованием агрегации ролей показано на рисунке 2.23. Делегированием называется механизм реализации, в котором объект, ответственный за операцию, пересылает (делегирует) эту операцию другому объекту; в объектно-ориентированных языках делегирование реализуется путем присоединения методов непосредственно к объектам, а не к классам.

В рассматриваемом примере операции классов оплата_труда и пенсионное_обеспечение делегируются объектам класса служащий, который можно рассматривать как результат агрегации классов оплата_труда и пенсионное_обеспечение. Более подробно делегирование будет рассмотрено в разделе 5.

Рис. 2.22. Реализация множественного наследования с помощью вложенного простого наследования

Рис. 2.23. Реализация множественного наследования путем делегирования с использованием агрегации ролей

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

Рис. 2.24. Реализация множественного наследования с использованием простого наследования и делегирования

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

  • если подкласс имеет несколько суперклассов, каждый из которых одинаково существен, лучше всего использовать делегирование (рисунок 2.23);
  • если наиболее существенным является только один из суперклассов, а остальные не так важны, наилучшим способом является реализация множественного наследования через простое наследование и делегирование (рисунок 2.24);
  • если число возможных комбинаций групп наследуемых свойств невелико, можно использовать вложенное простое наследование (рисунок 2.22); в случае большого числа комбинаций этот способ применять не следует;
  • если один из суперклассов передает подклассу намного большее число свойств, чем остальные суперклассы, следует сохранить наследование по этому пути (это возможно в ситуациях, представленных на рисунках 2.22 и 2.24);
  • если решено использовать вложенное простое наследование, то на первый уровень вложенности следует поместить наиболее существенный по передаче свойств суперкласс, затем наиболее существенный из оставшихся суперклассов и т.д. (рисунок 2.22);
  • следует избегать использования вложенного простого наследования (рисунок 2.22), если это ведет к дублированию достаточно больших частей программы;
  • следует помнить, что только вложенное простое наследование (рисунок 2.22) обеспечивает полную тождественность множественному наследованию.

Множественное наследование

Множественное наследование (Multiple inheritance)— наследование от нескольких базовых классов одновременно.

Зачем оно может использоваться

Интерфейсы
Интерфейс — специальные классы, описывающие набор методов, но не имеющие данных и реализации. Например интерфейсами являются классы:

  • IDrawable (то, что можно нарисовать)
  • ISerializable (то, что можно записать в файл)

Тогда класс JPGPicture будет наследоваться от этих двух интерфейсов, т. е. методы обоих интерфейсов будут реализованы в классе JPGPicture.
Интерфейсы — наиболее популярное применение множественного наследования. Возможность использования множественного наследования в виде интерфейсов есть во всех классических ООП-языках. В некоторых языках программирования (Java, C#) есть только такой вид множественного наследования.

Наследование от нескольких полноценных классов
Допустим у нас есть классы Cow и Sniper, а мы хотим получить класс, который обладает данными и методами обоих классов. Назовем его CowSniper. Таким образом у нас есть возможность «скрещивать» классы.
Таким образом синтаксически множественное наследование почти не отличается от обычного.
Заметим , что важен порядок перечисления предков, потому как в зависимости от него:

  • представление объекта класса в памяти;
  • очередность вызова конструкторов и деструкторов.

Проблемы при множественном наследовании

Перекрытие имен функций
эта проблема есть как и в обычном наследовании, так и в множественном.
Пусть в классах Cow и Sniper были методы sleep(). Тогда код:

В строке 2 произойдет ошибка компиляции, т. к. компилятор не может выбрать какой метод sleep() ему вызывать (от Cow или от Sniper). Поэтому необходимо сообщимть ему правильный выбор: cs.Cow::sleep();

Читайте так же:  Компенсация за просроченную заработную плату

Перекрытие виртуальных функций
Теперь рассмотрим тот случай, если метод sleep() виртуальный в классах-предках.

В таком случае будут перегружены сразу оба метода (ведь у них одинаковая сигнатура). Подробнее о том почему это происходит написано в следующем пункте. Тогда в следующих строках кода:

В строках 3 и 4 вызовется один и тот же метод CowSniper::sleep();
С одной стороны это удобно, т. к. если методы называются одинаково, то скорее всего они делают что-то похожее. Тогда перегрузив один метод мы перегрузим сразу оба.
С другой стороны может возникнуть проблема, если класс-потомок должен реализовывать один и тот же виртуальный метод от нескольких базовых классов по-разному. Тогда такая перегрузка будет вредна, но стандартно по другому не поступить. В таких случаях используется следующий способ обойти это ограничение.
Если есть иерархия классов A,B,C. И в классах A и B есть некоторый виртуальный метод f() .

Добавим в эту иерархию еще два класса A1 и B1. В классах A1 и B1 создадим методы fA() и fB() соотвественно. Теперь в C будут методы fA() и fB() , которые необходимо перегрузить, причем код в них разный 😉

Теперь использовать эти классы можно так:

В строке 5 вызовется C::fA() , а в строке 6 — C::fB() . Обратите внимание на то, как перегружен метод f() в классах A1 и B1, которые ко всему прочему теперь являются абстрактными и невозможно создать их экземпляры. Цель достигнута.

Представление объекта в памяти
а) Как мы помним, в случае линейного наследования распределение полей классов для объекта класса наследника в памяти будет такое:
При этом если мы создадим три указателя:

То указывать они все будут на начало объекта в памяти:
и при этом все хорошо.

б) Теперь перейдем к рассмотрению простейшего примера множественного наследования:

При этом если создать объект класса C, то в памяти он будет выглядеть так:

Опять попробуем создать три указателя как и в случае линейного наследования:

Однако теперь указатели распределятся следующим образом:

Мы видим, что классы A и B фактически разделены, но ведь если они имеют виртуальные методы, то должны вызываться перегруженные виртуальные методы класса C. Для того, чтобы это происходило в каждом из них есть ссылка на таблицу виртуальных функций. Тогда получается что в классе C будет сразу два указателя на две различные таблицы виртуальных функций (кол-во указателей = кол-ву полиморфных предков). Получается, что таблиц виртуальных функций две. Это не совсем так, т. к. они просто лежат рядом в памяти, т. е. фактически таблица одна, но в ней могут быть повторения, например, виртуальный деструктор:

в) Теперь рассмотрим множественного наследования, если в графе наследования есть цикл:

В памяти объект класса D будет представлен так:

Возникает проблема дублирования полей класса A, т.к. фактически будет два разных объекта типа A. Значит при компиляции следующего кода произойдет ошибка, т.к. непонятно к какому полю обращаться.
Для того, чтобы указать, какой из классов A выбрать следует написать так: При этом если мы хотим использовать методы класса A, так же необходимо явно указывать которого их них. Вследствие этого даные в двух объектах A в пределах одного D могут стать различными.

Рассмотрим два случая такого наследования и посмотрим на возможные проблемы с наличием двойного объекта.
1) Такой эффект может быть полезен, если класс A является файловым потоком, а B и C это writer и reader соответственно. А класс D читает из С и пишет в B. Очевидно, что у B и C файлы могут быть различны (скорее всего так и есть).
2) Рассмотрим умный указатель (A = LinkCounter), который внутри себя сдержит счетчик. В таком случае в классе D возникает два счетчика, что может привести к печальным последствием, если в одном месте работать с одним из них, а в другом с другим.

Вообще такое наследование (с циклами в графе родства) называется бриллиантовым (diamond inheritance) или Ромбовидным.

Виртуальное наследование.
Рассмотрим его на примере следующей иерархии

Синтаксически виртуальное наследование почти не отличается от множественного:

При этом следующий код будет прекрасно работать:
А все потому, что классы будут выглядеть следующим образом:

Здась может появиться проблема преобразования указателя на D к указателю на C. Но этой проблемы нет. Необходимо разобраться каким образом это реализуется.
Дело в том, что при виртуальном наследовании добавляется виртуальная функция, возвращающая указатель на A. Фактически для программиста она не видна. Для пояснения обозначим ее за getA(). Стоит заметить что она будет различна в классах B,C и D.
Теперь при обращении к полю из A (допустим в нем поле int k ) код вида k = 10; будет автоматически преобразован в getA()->k = 10; .

Теперь рассмотрим очередность вызова конструкторов.
Логично предположить, что вызов конструкторов пройдет так
Но возникает проблема, ведь конструкторы B и C могут вызывать различные конструкторы A и с различными параметрами.
Для определенности было введено следующее правило: Конструктор A должен быть явно вызван в конструкторе D, при этом в конструкторах B и C вызов конструктора A опустится.

В связи с этим есть замечание: Нужно следить и понимать, что при виртуальном наследовании в конструкторах B и C может не вызваться конструктор A с разными параметрами.

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

Возможной заменой множественного наследования не от интерфейсов является агрегация:

Множественное наследование

Если порожденный класс наследует элементы одного базового класса, то такое наследование называется одиночным . Однако, возможно и множественное наследование. Множественное наследование позволяет порожденному классу наследовать элементы более, чем от одного базового класса. Синтаксис заголовков классов расширяется так, чтобы разрешить создание списка базовых классов и обозначения их уровня доступа:

Читайте так же:  Статья ук 435

Класс А обобщенно наследует элементы всех трех основных классов.

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

  • если в порожденном классе используется член с таким же именем, как в одном из базовых классов;
  • когда в нескольких базовых классах определены члены с одинаковыми именами.

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

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

  • конструкторы базовых классов в порядке их задания;
  • конструкторы членов, являющихся объектами класса;
  • конструктор порожденного класса.

Деструкторы вызываются в порядке обратном вызову конструкторов.

Базовый класс определяется как виртуальный заданием ключевого слова virtual в списке порождения перед именем базового класса или указанием типа наследования

class A : public Y, public Z <
int key;
public :
A( int i = 0) : Y(i + 2), Z(i + 3)
<
key = Y::key + Z::key;
>
int getkey( void ) < return (key); >
>;
int main() <
A object(4);
cout << "object.key = " << object.getkey();
cin.get();
return 0;
>

Результат выполнения

Как видим, объект класса X был создан только один раз. Значение поля key изменяется каждый раз при доступе к нему как через класс Y , так и через класс Z .

Если в строках 11 и 17 этого примера убрать слово virtual , то результат работы программы изменится следующим образом:

То есть в этом случае и объект класса Y , и объект класса Z , которые наследует объект класса A , будут содержать свою собственную копию объекта класса X .

Конструкторы и деструкторы при использовании виртуальных базовых классов выполняются в следующем порядке:

  • конструкторы виртуальных базовых классов выполняются до конструкторов не виртуальных базовых классов, независимо от того, как эти классы заданы в списке порождения;
  • если класс имеет несколько виртуальных базовых классов, то конструкторы этих классов вызываются в порядке объявления виртуальных базовых классов в списке порождения;
  • деструкторы виртуальных базовых классов выполняются после деструкторов не виртуальных базовых классов.

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

Примеры множественного наследования

Примеры множественного наследования

Множественное наследование это, по сути, прямое приложение уже рассмотренных принципов наследования, — класс вправе иметь произвольное число родителей. Однако, изучая этот вопрос более внимательно, можно обнаружить две интересные проблемы:

[x]. потребность в смене имен компонентов, которая может оказаться полезной и при единичном наследовании;

[x]. дублируемое (repeated) наследование, при котором два класса связаны отношением предок-потомок более чем одним способом.

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

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

Похожие главы из других книг

Проблема наследования

Проблема наследования Если существует необходимость наследовать от класса Singleton, то следует придерживаться определенных правил.Во-первых, класс-наследник должен переопределить метод Instance(), так, чтобы создавать экземпляр производного класса. Если не предполагается, что

Второй принцип: поддержка наследования в C#

Второй принцип: поддержка наследования в C# Теперь, после исследования различных подходов, позволяющих создавать классы с хорошей инкапсуляцией, пришло время заняться построением семейств связанных классов. Как уже упоминалось, наследование является принципом ООП,

Запрет наследования: изолированные классы

Запрет наследования: изолированные классы Создавая отношения базовых классов и подклассов, вы получаете возможность использовать поведение существующих типов. Но что делать, когда нужно определить класс, не позволяющий получение подклассов? Например, предположим, что

Цепочка наследования типа Page

Цепочка наследования типа Page Как вы только что убедились, готовый генерируемый класс, представляющий файл *.aspx, получается из System.Web.UI.Page. Подобно любому базовому классу, этот тип обеспечивает полиморфный интерфейс всем производным типам. Однако тип Page является не

Просмотр дерева наследования классов

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

Правило 39: Продумывайте подход к использованию закрытого наследования

Правило 39: Продумывайте подход к использованию закрытого наследования В правиле 32 показано, что C++ рассматривает открытое наследование как отношение типа «является». В частности, говорится, что компиляторы, столкнувшись с иерархией, где класс Student открыто наследует

Правило 40: Продумывайте подход к использованию множественного наследования

Правило 40: Продумывайте подход к использованию множественного наследования Когда речь заходит о множественном наследовании (multiple inheritance – MI), сообщество разработчиков на C++ разделяется на два больших лагеря. Одни полагают, что раз одиночное исследование (SI) – это хорошо,

Смысл наследования

Смысл наследования Мы уже рассмотрели основные способы наследования. Многое еще предстоит изучить, в частности, множественное наследование и детали того, что происходит с утверждениями в контексте наследования (понятие субконтрактов).Но вначале следует поразмышлять

Лекция 16. Техника наследования

Лекция 16. Техника наследования Наследование — ключевая составляющая ОО-подхода к повторному использованию и расширяемости. В этой лекции нам предстоит исследовать новые возможности, разнородные, но демонстрирующие замечательные следствия красоты базисных идей.

Читайте так же:  Детские пособия 2019 калининград

Глобальная структура наследования

Глобальная структура наследования Ранее мы уже ссылались на универсальные (universal) классы GENERAL и ANY, а также на безобъектный (objectless) класс NONE. Пришло время пояснить их роль и представить глобальную структуру

2.2.4. Типы сущностей и иерархия наследования

2.2.4. Типы сущностей и иерархия наследования Как было указано выше, связи определяют, является ли сущность независимой или зависимой. Различают несколько типов зависимых сущностей.Характеристическая — зависимая дочерняя сущность, которая связана только с одной

35. Избегайте наследования от классов, которые не спроектированы для этой цели

35. Избегайте наследования от классов, которые не спроектированы для этой цели РезюмеКлассы, предназначенные для автономного использования, подчиняются правилам проектирования, отличным от правил для базовых классов (см. рекомендацию 32). Использование автономных

Глава 8. Изучение наследования

Глава 8. Изучение наследования НаследованиеНаследованием (inheritance) называется такое отношение между классами, когда один класс использует часть структуры и/или поведения другого или нескольких классов. При наследовании создается иерархия абстракций, в которой подкласс

Синтаксис множественного фона

18.6. Пример множественного виртуального наследования A

18.6. Пример множественного виртуального наследования A Мы продемонстрируем определение и использование множественного виртуального наследования, реализовав иерархию шаблонов классов Array (см. раздел 2.4) на основе шаблона Array (см. главу 16), модифицированного так, чтобы он

19. Применение наследования в C++

19. Применение наследования в C++ При использовании наследования указатель или ссылка на тип базового класса способен адресовать объект любого производного от него класса. Возможность манипулировать такими указателями или ссылками независимо от фактического типа

Множественное наследование в Java. Сравнение композиции и наследования

Множественное наследование в Java

«Проблема Алмаза»

Множественное наследование и интерфейсы

Композиция как спасение

Композиция или наследование?

Предположим, мы имеем следующую связку классов родитель-наследник:

Код выше прекрасно компилируется и работает, но что, если ClassC будет реализован иначе:

Отметьте, что метод test() уже существует в классе наследнике, но возвращает результат другого типа. Теперь ClassD , в случае если вы используете IDE, не будет скомпилирован. Вам будет рекомендовано изменить тип возвращаемого результата в классе наследнике или в суперклассе.

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

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

Следующая проблема с наследованием заключается в том, что мы демонстрируем все методы родителя клиенту. И если суперкласс разработан не очень корректно и содержит дыры в «безопасности». Тогда, несмотря на то, что мы полностью позаботимся о безопасности при реализации нашего подкласса, мы все равно будем зависеть от ущербной реализации класса-родителя.

Композиция помогает нам в обеспечении контролируемого доступа к методам суперкласса, тогда как наследование не поддерживает никакого контроля над его методами. Это — также одно из главных преимуществ композиции над наследованием.

Еще одно преимущество композиции то, что оно добавляет гибкости при вызове методов. Реализация класса ClassC , описанная выше, не оптимальна и использует раннее связывание с вызываемым методом. Минимальные изменения позволят нам сделать вызов методов гибким и обеспечить позднее связывание (связывание во время выполнения).

Программа выше выведет на экран:

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

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

В идеале, наследование следует использовать только тогда, когда отношение ”is-a” справедливо для классов родителя и наследника, иначе стоит предпочесть композицию.

Множественное наследование

Создайте программу:
с базовым классом Справочник, который включает в себя следующие данные-элементы о сотрудниках:
• Наименование продукции;
• Код продукции;
базовым классом Остатки на начало месяца:
• Остатки на начало месяца;
Производный класс Сведения о выпуске изделий, который состоит из:
• Наименование продукции;
• Код продукции;
• Остатки на начало месяца;
• План выпуска;
• Остатки на конец месяца;
• Функция F, которая считает Объем реализации = Остатки на начало года + План выпуска – Остатки на конец года.

Пример
Пример множественного наследования:
• Если вы порождаете класс из нескольких базовых классов, то получаете преимущества множественного наследования.
• При множественном наследовании производный класс получает атрибуты двух или более классов.
• При использовании множественного наследования для порождения класса конструктор производного класса должен вызвать конструкторы всех базовых классов.
• При порождении класса из производного класса вы создаете иерархию наследования (иерархию классов).
Множественное наследование является мощным инструментом объектно-ориентированного программирования.
Предположим, к примеру, у вас есть класс computer_screen:
Предположим, что у вас есть также класс mother_board:
Используя эти два класса, можно породить класс computer, что показано ниже:
Как видите, этот класс указывает свои базовые классы сразу после двоеточия, следующего за именем класса computer.
class computer : public computer_screen, public mother_board // ———> Базовые классы
Следующая программа COMPUTER. CPP порождает класс computer, используя базовые классы computer_screen и mother_board: