Лично я научился всегда отдавать предпочтение композиции, а не наследованию. Нет программной проблемы, которую вы можете решить с помощью наследования, которую вы не можете решить с помощью композиции; хотя в некоторых случаях вам, возможно, придется использовать интерфейсы (Java) или протоколы (Obj-C). Поскольку C ++ не знает ничего подобного, вам придется использовать абстрактные базовые классы, что означает, что вы не можете полностью избавиться от наследования в C ++.
Композиция часто более логична, она обеспечивает лучшую абстракцию, лучшую инкапсуляцию, лучшее повторное использование кода (особенно в очень больших проектах) и с меньшей вероятностью что-либо сломает на расстоянии только потому, что вы произвели изолированное изменение в любом месте своего кода. Это также облегчает соблюдение « принципа одиночной ответственности », который часто обобщается как «. Никогда не должно быть более одной причины для изменения класса. », и это означает, что каждый класс существует для определенной цели, и у него должны быть только методы, которые непосредственно связаны с его целью. Кроме того, наличие очень мелкого дерева наследования значительно упрощает обзор, даже когда ваш проект начинает становиться действительно большим. Многие люди думают, что наследование представляет наш реальный мир довольно хорошо, но это не правда. Реальный мир использует гораздо больше композиции, чем наследования. Практически каждый объект реального мира, который вы можете держать в руке, был составлен из других меньших объектов реального мира.
Хотя есть и недостатки в композиции. Если вы вообще пропустите наследование и сконцентрируетесь только на композиции, вы заметите, что вам часто приходится писать пару дополнительных строк кода, в которых не было необходимости, если вы использовали наследование. Вы также иногда вынуждены повторяться, и это нарушает СУХОЙ принцип (СУХОЙ = Не повторяйте себя). Кроме того, для композиции часто требуется делегирование, а метод просто вызывает другой метод другого объекта без какого-либо другого кода, окружающего этот вызов. Такие «двойные вызовы методов» (которые могут легко распространяться на тройные или четырехкратные вызовы методов и даже дальше) имеют гораздо худшую производительность, чем наследование, когда вы просто наследуете метод своего родителя. Вызов унаследованного метода может быть таким же быстрым, как и вызов не унаследованного, или он может быть немного медленнее, но обычно все же быстрее, чем два последовательных вызова метода.
Возможно, вы заметили, что большинство ОО-языков не допускают множественное наследование. Хотя есть пара случаев, когда множественное наследование может действительно что-то купить, но это скорее исключения, чем правило. Всякий раз, когда вы сталкиваетесь с ситуацией, когда вы думаете, что «множественное наследование было бы действительно полезной функцией для решения этой проблемы», вы обычно находитесь в точке, когда вам следует полностью переосмыслить наследование, поскольку даже для этого может потребоваться пара дополнительных строк кода. решение, основанное на композиции, обычно оказывается гораздо более элегантным, гибким и пригодным для будущего.
Наследование - действительно классная особенность, но я боюсь, что оно использовалось последние пару лет. Люди относились к наследованию как к единому молотку, который может все это пригвоздить, независимо от того, был ли это на самом деле гвоздь, болт или что-то совершенно другое.