Множественное наследование (сокращенно MI) пахнет , что означает, что обычно , это было сделано по дурным причинам, и оно отразится на лице сопровождающего.
Резюме
- Рассмотрим состав признаков вместо наследования
- Остерегайся Алмаза Ужаса
- Рассмотрим наследование нескольких интерфейсов вместо объектов
- Иногда множественное наследование является правильным. Если это так, используйте его.
- Будьте готовы защищать вашу множественную наследуемую архитектуру в обзорах кода
1. Возможно композиция?
Это верно для наследования, и поэтому еще более верно для множественного наследования.
Действительно ли ваш объект должен наследоваться от другого? Car
не нужно наследовать от Engine
для работы или от Wheel
. A Car
имеет Engine
и четыре Wheel
.
Если вы используете множественное наследование для решения этих проблем вместо компоновки, значит, вы сделали что-то не так.
2. Алмаз ужаса
Обычно у вас есть класс A
, тогда B
и C
оба наследуются от A
. И (не спрашивайте меня почему), кто-то тогда решает, что D
должен наследовать от B
и C
.
Я сталкивался с такой проблемой дважды за восемь восьмых лет, и это забавно видеть из-за:
- Сколько было ошибки с самого начала (* В обоих случаях
D
не должен был наследоваться от B
и C
), потому что это была плохая архитектура (на самом деле, C
не должна существовал вообще ...)
- Сколько платят сопровождающие за это, потому что в C ++ родительский класс
A
присутствовал дважды в классе внуков D
, и, таким образом, обновление одного родительского поля A::field
означало либо его обновление дважды (через B::field
и C::field
), или что-то пошло не так и произойдет сбой, позже (новый указатель в B::field
и удаление C::field
...)
Использование ключевого слова virtual в C ++ для определения наследования позволяет избежать двойной структуры, описанной выше, если это не то, что вам нужно, но в любом случае, по моему опыту, вы, вероятно, делаете что-то не так ...
В иерархии объектов вы должны попытаться сохранить иерархию в виде дерева (узел имеет ОДНОГО родителя), а не в виде графика.
Подробнее о бриллианте (изменить 2017-05-03)
Реальная проблема с Diamond of Dread в C ++ ( при условии, что дизайн продуман - проверьте ваш код! ), заключается в том, что вам нужно сделать выбор :
- Желательно ли, чтобы класс
A
существовал дважды в вашем макете, и что это значит? Если да, то непременно наследуй от него дважды.
- если он должен существовать только один раз, то наследовать от него виртуально.
Этот выбор присущ данной проблеме, и в C ++, в отличие от других языков, вы можете сделать это без догматического навязывания своего дизайна на уровне языка.
Но, как и все силы, с этой силой приходит ответственность: пересмотреть свой дизайн.
3. Интерфейсы
Множественное наследование нуля или одного конкретного класса и нуля или более интерфейсов обычно хорошо, потому что вы не столкнетесь с Diamond of Dread, описанным выше. На самом деле, именно так все и делается в Java.
Обычно, что вы имеете в виду, когда C наследует от A
и B
, что пользователи могут использовать C
, как если бы это был A
, и / или как если бы это был B
.
В C ++ интерфейс - это абстрактный класс, который имеет:
- весь его метод объявлен чисто виртуальным (с добавлением = 0) (удалено 2017-05-03)
- нет переменных-членов
Множественное наследование от нуля до одного реального объекта и от нуля или более интерфейсов не считается "вонючим" (по крайней мере, не так много).
Подробнее об абстрактном интерфейсе C ++ (редакция 2017-05-03)
Во-первых, шаблон NVI можно использовать для создания интерфейса, поскольку реальный критерий - отсутствие состояния (т. Е. Никаких переменных-членов, кроме this
). Цель вашего абстрактного интерфейса - опубликовать контракт («ты можешь называть меня так и так»), ни больше, ни меньше. Ограничение наличия только абстрактного виртуального метода должно быть выбором проекта, а не обязательством.
Во-вторых, в C ++ имеет смысл наследовать виртуально от абстрактных интерфейсов (даже с дополнительными затратами / косвенностью). Если вы этого не сделаете, а наследование интерфейса появляется в вашей иерархии несколько раз, то у вас будут неоднозначности.
В-третьих, объектная ориентация великолепна, но это не Единственная правда, существующая там TM в C ++. Используйте правильные инструменты и всегда помните, что у вас есть другие парадигмы в C ++, предлагающие различные виды решений.
4. Вам действительно нужно множественное наследование?
Иногда да.
Обычно ваш класс C
наследуется от A
и B
, а A
и B
являются двумя не связанными объектами (т.е. не в одной иерархии, ничего общего, разных концепций и т. Д.). ).
Например, у вас может быть система Nodes
с координатами X, Y, Z, способная выполнять множество геометрических вычислений (возможно, точка, часть геометрических объектов), и каждый узел является Автоматизированным агентом, способным общаться с другими агентами.
Возможно, у вас уже есть доступ к двум библиотекам, каждая из которых имеет собственное пространство имен (еще одна причина использовать пространства имен ... Но вы используете пространства имен, не так ли?), Одна из которых geo
, а другая ai
Таким образом, у вас есть собственный own::Node
производный от ai::Agent
и geo::Point
.
Это момент, когда вы должны спросить себя, не следует ли вам вместо этого использовать композицию. Если own::Node
действительно действительно ai::Agent
и geo::Point
, то композиция не подойдет.
Тогда вам потребуется множественное наследование, чтобы ваш own::Node
мог общаться с другими агентами в соответствии с их положением в трехмерном пространстве.
(Вы заметите, что ai::Agent
и geo::Point
полностью, полностью, полностью НЕ СВЯЗАНЫ ... Это резко снижает опасность множественного наследования)
Другие случаи (изменить 2017-05-03)
Есть и другие случаи:
- использование (возможно, частного) наследования в качестве детали реализации
- некоторые идиомы C ++, такие как политики, могут использовать множественное наследование (когда каждая часть должна взаимодействовать с другими через
this
)
- виртуальное наследование от std :: exception ( Требуется ли виртуальное наследование для исключений? )
- и т.д.
Иногда вы можете использовать композицию, а иногда MI лучше. Дело в том, что у вас есть выбор. Сделайте это ответственно (и пересмотрите свой код).
5. Итак, я должен сделать множественное наследование?
Большую часть времени, по моему опыту, нет. MI - не тот инструмент, даже если кажется, что он работает, потому что он может быть использован ленивым для объединения функций без осознания последствий (например, создание Car
и Engine
и Wheel
).
Но иногда да. И в то время ничто не будет работать лучше, чем МИ.
Но поскольку MI вонючий, будьте готовы защищать свою архитектуру в обзорах кода (и защищать ее - это хорошо, потому что, если вы не можете защитить ее, то вам не следует это делать).