Pull to refresh

Comments 12

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

Конечно есть моменты, когда чётко предельно видно, что нужно использовать наследование/композицию, а во всех остальных случаях советуют экспериментировать, использовать и наследование, и композицию вместе.
К сожалению, эту истину знают не все и часто слепо следуют каким-то правилам и паттернам. Я просто постарался показать, что религия в разработке — не есть хорошо, и сформулировать плюсы и минусы.
Статья помещена в хаб C++, поэтому нужно внести уточнения.

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

В C++ это не так, потому что при композиции в объекте хранятся сами подобъекты, а не указатели на них (как это происходит в Java в общем случае). Если подобъект мал, то его имеет смысл хранить не через указатель, а напрямую. Если хочется при этом сохранять бинарную совместимость между версиями кода, то можно в объекте-хозяине хранить только указатель, а все поля запихнуть в структуру, доступную через этот указатель в коде реализации (pointer to implementation). Часто это более мудрый путь, чем хранить каждое из полей через указатель.

Возможность смены агрегируемого объекта в runtime.

Чтобы воспользоваться данным преимуществом в C++, нужно хранить в объекте указатели на подобъекты или использовать подобъекты одного типа.
при композиции в объекте хранятся сами подобъекты, а не указатели на них

может, наоборот — обычно, в объекте хранятся указатели на подобъекты, а не сами подобъекты, но если объкт мал, то его имеет смысл хранить не через указатель, а напрямую.
В Java хранить подобъект напрямую (не через указатель) нельзя, насколько мне известно. А в C++ поля хранятся не через указатель, поэтому вариант, когда подобъект хранится через указатель, можно рассматривать как поле, содержащее указатель. Какой из способов применяется чаще (подобъект как поле, подобъект через указатель, pimpl (все подобъекты через 1 указатель)), зависит от конкретного случая.
Если подобъект мал, то его имеет смысл хранить не через указатель, а напрямую.


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


Спорный момент. Обычно такая ситуация говорит о том, что что-то прогнило в Датском королевстве.
Возможно спорный, да. Но с другой стороны, почему не может быть ф-ции, которая выполняет какую-либо сложную операцию, основываясь на нескольких поведениях объекта, и при этом, какое-то одно из этих поведений является единственно необходимым, а другие являются вспомогательными и служат для какой-то коррекции результата. При наследовании мы можем «отфильтровать» неподходящие объекты на этапе компиляции. Более того, мы можем создать некий условный пустой класс, наследующий сразу от нескольких поведений и «фильтровать» таким образом сразу по нескольким поведениям, обеспечив гарантию того, что в ф-цию будут приходить объекты, обладающие всеми необходимыми свойствами. При композиции для этого всё равно придётся прибегать к наследованию от дополнительных интерфейсов…
Опыт показывает, что если возникают такие сложности с многообразием поведения, хранением сложного состояния и т.д., то у приложения запутанная архитектура. Иногда стремление к излишней гибкости приводит к чрезмерному усложнению и с этим надо бороться в первую очередь, а не пытаться лечить симптомы эмуляцией множественного наследования или ссылками на объект-владелец.

Как правило, когда удается придумать удачную архитектуру, потом не возникает подобных проблем ни с наследованием, ни с композицией. KISS!
Я написал выше (в ответе Dair_Targ'у) пример ситуации. К какой архитектуре можно прибегнуть в данном случае? Хотя, если Вы говорите о C# или Java, то там композиция конечно намного эффективнее. Но, имхо, в ряде случае, она там — не пример удачной архитектуры, а «костыль» в отсутствие множественного наследования. Но даже в этих языках, если ни один их обозначенных плюсов не используется, то (опять же, имхо) наследование предпочтительнее. Оно только упросит код.
Наследование от аггрегации отличается необходимостью менять интерфейс, а не только реализацию, при изменении базового класса. И похоже на композицию тем, что требует умения создавать экземпляр базового класса.

Только, чтобы это всё понять, необходимо «перевернуть» «содержащий» класс и «содержащийся» — когда речь идёт о наследовании, все программисты путают направленность отношения. Дочерний класс является «содержащим», а родительский — «содержащимся» в дочернем. Это кажется непривычным, но ставит мозги на место, а понятия — по полочкам. Я всю ночь разбирался вот, чтоб понять отличия ассоциации, отношения, аггрегации, композиции и наследования. Заодно UML наконец понял, что там за стрелки странные :-)
Sign up to leave a comment.

Articles