Откуда взялась эта концепция «отдавать предпочтение композиции перед наследованием»?

143

В последние несколько месяцев, мантра «композиция благосклонности вместо наследования», кажется, возникла из ниоткуда и стала почти своего рода мемом в сообществе программистов. И каждый раз, когда я вижу это, я немного озадачен. Это как кто-то сказал: «Скорее тренируйся, чем молотки». По моему опыту, состав и наследование - это два разных инструмента с разными вариантами использования, и рассматривать их так, как если бы они были взаимозаменяемыми, а один по своей сути превосходил другой, не имеет смысла.

Кроме того, я никогда не вижу реального объяснения того, почему наследование плохо, а композиция хороша, что делает меня более подозрительным. Это должно быть просто принято на веру? Подстановка и полиморфизм Лискова обладают хорошо известными, четкими преимуществами, и IMO составляет весь смысл использования объектно-ориентированного программирования, и никто никогда не объясняет, почему их следует отбрасывать в пользу композиции.

Кто-нибудь знает, откуда взялась эта концепция и в чем ее обоснование?

Мейсон Уилер
источник
45
Как уже отмечали другие, это было давно - я удивлен, что вы только что услышали об этом. Он интуитивно понятен любому, кто строит большие системы на таких языках, как Java, в течение любого времени. Это ядро ​​любого интервью, которое я когда-либо давал, и когда кандидат начинает говорить о наследовании, я начинаю сомневаться в его уровне квалификации и количестве опыта. Вот хорошее введение , почему наследование является хрупким решением (есть много много других): artima.com/lejava/articles/designprinciples4.html
Janx
6
@Janx: Может быть, это все. Я не строю большие системы на таких языках, как Java; Я строю их в Delphi, и без подстановки и полиморфизма Лискова мы бы никогда ничего не сделали. Его объектная модель в некоторых отношениях отличается от Java или C ++, и многие проблемы, которые этот принцип, по-видимому, должен решить, на самом деле не существуют или являются гораздо менее проблемными в Delphi. Я думаю, разные точки зрения с разных точек зрения.
Мейсон Уилер
14
Я провел несколько лет в построении команды относительно больших систем в Delphi, и высокие деревья наследования, конечно, кусали нашу команду и причиняли нам значительную боль. Я подозреваю, что ваше внимание к принципам SOLID помогает избежать проблемы, а не использования Delphi.
Беван
8
Последние несколько месяцев?!?
Jas
6
ИМХО, эта концепция никогда не была полностью адаптирована к разнообразию языков, которые поддерживают как наследование интерфейса (т. Е. Подтип с чистыми интерфейсами), так и наследование реализации. Слишком много людей следуют этой мантре и не используют достаточно интерфейсов.
Ури

Ответы:

136

Хотя я думаю, что я слышал обсуждения «состав против наследства» задолго до GoF, я не могу указать на конкретный источник. В любом случае, возможно, это Бух.

<Напыщенная>

Ах, но, как и многие мантры, эта выродилась по типичной линии:

  1. он представлен с подробным объяснением и аргументацией уважаемого источника, который называет эту фразу как напоминание о первоначальном сложном обсуждении
  2. он поделился со знатоком «подмигивающего» подмигившего на какое-то время немного осведомленных, как правило, когда комментирует ошибки n00b
  3. вскоре его повторяют бездумно тысячи за тысячами, которые никогда не читают объяснения, но любят использовать его как оправдание, чтобы не думать , и как дешевый и простой способ почувствовать себя выше других.
  4. в конце концов, никакое разумное разоблачение не сможет остановить поток «мемов» - и парадигма выродится в религию и догму.

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

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

Пожалуйста, ДУМАЙТЕ о своих проектах, и прекратите извергать лозунги.

</ Декламация>

Стивен А. Лоу
источник
16
Буч утверждает, что наследование реализации создает высокую связь между классами. С другой стороны, он считает наследование ключевой концепцией, которая отличает ООП от процедурного программирования.
Неманя Трифунович
13
Там должно быть слово для такого рода вещей. Может быть, преждевременная регургитация ?
Йорг Миттаг
8
@ Йорг: Вы знаете, что будет с таким термином, как преждевременная регургитация? Именно то, что объяснено выше. :) (Кстати, когда регургитация никогда не бывает преждевременной?)
Бьярке Фрейнд-Хансен
6
@Nemanja: Верно. Вопрос в том, действительно ли эта связь плохая. Если классы концептуально сильно связаны и концептуально формируют отношения супертип-подтип, и никогда не могут быть концептуально отделены, даже если они формально отделены на уровне языка, тогда сильная связь - это хорошо.
dsimcha
5
Мантра проста: «То, что вы можете придумать что-то вроде игры, не означает, что вы должны делать все из игры». ;)
Эван Плейс,
73

Опыт.

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

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

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

Стивен Бейли
источник
7
В общем, вы не можете сказать «вы не должны использовать ООП в первую очередь, если вы не понимаете подстановку Лискова», поэтому вы говорите «отдавайте предпочтение композиции, а не наследованию», вместо этого пытаясь ограничить ущерб, причиненный некомпетентным кодеры?
Мейсон Уилер
13
@ Мейсон: Как и любая мантра, «отдавай предпочтение композиции, а не наследованию» ориентирована на начинающих программистов. Если вы уже знаете, когда использовать наследование, а когда использовать композицию, повторять подобные мантры бессмысленно.
Дин Хардинг
7
@ Дин - я не уверен, что новичок такой же, как тот, кто не понимает лучшие практики наследования. Я думаю, что есть нечто большее, чем это. Плохое наследование является причиной многих, многих головных болей в моей работе, и это не код, написанный программистами, которых можно считать «новичками».
Николь
6
@Renesis: Первое, о чем я думаю, когда я слышу, это то, что «у некоторых людей есть десятилетний опыт, а у некоторых один год повторяется десять раз».
Мейсон Уилер
8
Я разрабатывал довольно большой проект сразу после первого выхода «Getting» и очень полагался на него. Несмотря на то, что это работало хорошо, я постоянно находил дизайн хрупким - вызывая незначительные раздражения тут и там, а иногда превращая определенные рефакторы в сучку. Трудно объяснить весь этот опыт, но фраза «Изобилие благосклонности по наследству» описывает его ТОЧНО. Обратите внимание, что он не говорит и даже не подразумевает «Избегать наследования», он просто дает вам небольшое толчок, когда выбор не очевиден.
Билл К
43

Это не новая идея, я думаю, что она на самом деле была представлена ​​в книге шаблонов дизайна GoF, которая была опубликована в 1994 году.

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

Из книги GoF:

Наследование предоставляет подклассу детали реализации его родителя, часто говорят, что «наследование нарушает инкапсуляцию»

Статья в Википедии о книге GoF имеет достойное введение.

Дин Хардинг
источник
14
Я не согласен с этим. Вам не нужно знать детали реализации класса, от которого вы наследуете; только общественность и защищенные члены, выставленные классом. Если вам нужно знать детали реализации, то вы или кто-либо другой, кто написал базовый класс, делают что-то не так, и если недостаток в базовом классе, композиция не поможет вам исправить / обойти это.
Мейсон Уилер
18
Как вы можете не согласиться с тем, что вы не читали? Есть целая страница с половиной обсуждения от GoF, о которой вы получаете лишь крошечное представление.
Philodadad
7
@pholosodad: Я не согласен с тем, что я не читал. Я не согласен с тем, что написал Дин: «По определению вам нужно знать детали реализации класса, от которого вы наследуете», который я прочитал.
Мейсон Уилер
10
То, что я написал, это просто краткое изложение того, что описано в книге GoF. Я мог бы сформулировать это немного решительно (вам не нужно знать все детали реализации), но это основная причина, почему GoF говорит, что предпочтение композиции перед наследованием.
Дин Хардинг
2
Поправьте меня, если я ошибаюсь, но я вижу это как "одобрение явных отношений классов (т.е. интерфейсов) над неявными (то есть наследование)". Первые говорят вам, что вам нужно, не говоря вам, как это сделать. Последний не только скажет вам, как это сделать, но и заставит вас сожалеть об этом в будущем.
Эван Плейс,
26

Чтобы ответить на часть вашего вопроса, я полагаю, что эта концепция впервые появилась в книге « Шаблоны проектирования GOF : элементы многоразового объектно-ориентированного программного обеспечения» , которая была впервые опубликована в 1994 году.

Пользуешься композицией объекта, а не наследованием

Они предваряют это утверждение кратким сравнением наследования с композицией. Они не говорят «никогда не используйте наследство».

vjones
источник
23

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

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

Это только означает, что с наследованием следует обращаться осторожно, потому что оно обходится дорого, а не в том, что оно бесполезно. На самом деле, «Композиция по наследованию» часто заканчивается тем, что «Композиция + наследование по наследованию», поскольку вы часто хотите, чтобы ваша составная зависимость была абстрактным суперклассом, а не конкретным подклассом. Это позволяет вам переключаться между различными конкретными реализациями вашей зависимости во время выполнения.

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

(Метафорический) пример может быть:

«У меня есть класс Snake, и я хочу включить в этот класс то, что происходит, когда кусается Змея. Я хотел бы, чтобы Snake унаследовал класс BiterAnimal, имеющий метод Bite (), и переопределил этот метод для отражения ядовитого прикуса. Но Composition over Inheritance предупреждает меня, что я должен попытаться использовать вместо этого композицию ... В моем случае это может привести к тому, что Snake будет иметь член Bite. Класс Bite может быть абстрактным (или интерфейсом) с несколькими подклассами. Это позволит мне нравятся такие вещи, как наличие подклассов VenomousBite и DryBite и возможность изменять прикус в том же экземпляре Snake, когда змея подрастает. Плюс обработка всех эффектов Bite в своем отдельном классе может позволить мне повторно использовать его в этом классе Frost , потому что мороз кусает, но не BiterAnimal, и так далее ... "

guillaume31
источник
4
Очень хороший пример со Змеей. Недавно я встречался с подобным случаем на своих занятиях
Макси,
22

Некоторые возможные аргументы для композиции:

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

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

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

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

Отказ от ответственности:
Очевидно, это не эта четкая / односторонняя улица. Каждый дизайн заслуживает оценки нескольких шаблонов / инструментов. Наследование широко используется, имеет много преимуществ и во много раз более элегантно, чем композиция. Это всего лишь несколько возможных причин, которые можно использовать при выборе композиции.

TJB
источник
1
«Очевидно, это не эта улица с односторонним движением». Когда композиция не более (или в равной степени) гибкая, чем наследование? Я считаю , что это очень это одна улица с односторонним движением. Наследование - это просто синтаксический сахар для особого случая композиции.
weberc2
Композиция часто не является гибкой при использовании языка, который реализует композицию с использованием явно реализованных интерфейсов, поскольку эти интерфейсы не могут развиваться обратно совместимым образом с течением времени. #jmtcw
MikeSchinkel
11

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

Суть концепции в том, что наследование сопряжено с большими концептуальными издержками. Когда вы используете наследование, то каждый вызов метода имеет неявную диспетчеризацию. Если у вас есть глубокие деревья наследования, или многократная отправка, или (что еще хуже) и то, и другое, то выяснение, куда будет отправляться конкретный метод в любом конкретном вызове, может стать королевской PITA. Это усложняет правильные рассуждения о коде и усложняет отладку.

Позвольте мне привести простой пример для иллюстрации. Предположим, что глубоко в дереве наследования кто-то назвал метод foo. Затем приходит кто-то другой и добавляет fooнаверху дерева, но делает что-то другое. (Этот случай чаще встречается при множественном наследовании.) Теперь этот человек, работающий с корневым классом, сломал неясный дочерний класс и, вероятно, не осознает этого. Вы можете иметь 100% охват модульными тестами и не заметить эту поломку, потому что человек наверху не будет думать о тестировании дочернего класса, а тесты для дочернего класса не думают о тестировании новых методов, созданных наверху , (По общему признанию, есть способы написать модульные тесты, которые поймут это, но есть также случаи, когда вы не можете легко написать тесты таким образом.)

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

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

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

btilly
источник
3
Хорошо, я думаю, это имеет смысл с точки зрения C ++. Об этом я никогда не задумывался, потому что это не проблема в Delphi, которым я пользуюсь большую часть времени. (Нет множественного наследования, и если у вас есть метод в базовом классе и другой метод с тем же именем в производном классе, который не переопределяет базовый метод, компилятор выдаст предупреждение, так что вы случайно не закончите с такого рода проблемами.)
Мейсон Уилер
1
@Mason: версия Анкара Object Pascal (он же Delphi) превосходит C ++ и Java, когда дело касается проблемы наследования хрупких базовых классов. В отличие от C ++ и Java, перегрузка унаследованного виртуального метода не является неявной.
bit-twiddler
2
@ bit-twiddler, то, что вы говорите о C ++ и Java, можно сказать о Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C и любом другом языке, который я изучил, который обеспечивает любую форму поддержки ОО. Тем не менее, поиск в Google предполагает, что C # следует примеру Object Pascal.
btilly
3
Это потому, что Андерс Хейлсберг разработал аромат Borland для Object Pascal (он же Delphi) и C #.
bit-twiddler
8

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

Наследование от класса подразумевает много вещей. Это подразумевает, что Derived является типом Base (см. Принцип подстановки Liskov для подробностей), в котором всякий раз, когда вы используете Base, имеет смысл использовать Derived. Он дает Производный доступ к защищенным членам и функциям-членам Базы. Это тесная связь, то есть она имеет высокую степень связи, и изменения одного из них, скорее всего, потребуют изменений другого.

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

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

Дэвид Торнли
источник
«В других обстоятельствах наследование сработает, а композиция не сработает». Когда? Наследование может быть смоделировано с использованием композиции и интерфейса полиморфизма, поэтому я был бы удивлен, если есть какие-либо обстоятельства, для которых композиция не будет работать.
weberc2
8

Вот мои два цента (помимо всех превосходных моментов, уже поднятых):

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

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

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

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

В 80% случаев это достаточно хорошо. Но остальные 20% находятся там, где начинается проблема. Во избежание переписывания и для обеспечения того, чтобы они воспользовались преимуществами совместного использования реализации, они начинают проектировать свою иерархию вокруг предполагаемой реализации, а не базовых абстракций. «Стек наследует из двусвязного списка» является хорошим примером этого.

Большинство учителей не настаивают на представлении концепции интерфейсов на этом этапе, потому что это либо другая концепция, либо потому что (в C ++) вы должны притворяться, используя абстрактные классы и множественное наследование, которое не преподается на данном этапе. В Java многие учителя не различают «отсутствие множественного наследования» или «множественное наследование - это зло» от важности интерфейсов.

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

Uri
источник
Это настоящая часть образования. Вы должны пройти более высокие курсы, чтобы получить то есть оставшиеся 20%, но вы, как студент, только что учили базовые и, возможно, промежуточные курсы. В финале вы чувствуете себя хорошо, потому что вы хорошо сделали свои курсы (и просто не видите это просто булавка на лестнице). Это всего лишь часть реальности, и лучше всего понимать ее побочные эффекты, а не атаковать кодеров.
Независимо
8

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

Я надеюсь, что это так.

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

В большинстве ОО-языков при наследовании от базового класса вы:

  • наследовать от своего интерфейса
  • наследовать от его реализации (как данные, так и методы)

И тут становятся неприятности.

Это не единственный подход, хотя 00-языковые версии в основном придерживаются его. К счастью, интерфейсы / абстрактные классы существуют в тех.

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

В качестве контрапункта Haskellразрешается использовать принцип Лискова только при «выводе» из чистых интерфейсов (так называемых концепций) (1). Вы не можете наследовать от существующего класса, только композиция позволяет встраивать его данные.

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

Матье М.
источник
7

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

Джеффри Фауст
источник
2
Но в этом все дело. Они не "иначе эквивалентны". Наследование позволяет использовать замену и полиморфизм Лискова, что является основной целью использования ООП. Композиция не делает.
Мейсон Уилер
4
Полиморфизм / LSP - это не «суть» ООП, это одна особенность. Существует также инкапсуляция, абстракции и т. Д. Наследование подразумевает отношения «есть», модели агрегации - отношения «имеет».
Стив
@Steve: Вы можете делать абстракции и инкапсуляцию в любой парадигме, которая поддерживает создание структур данных и процедур / функций. Но полиморфизм (в частности, относящийся к диспетчеризации виртуальных методов в этом контексте) и подстановка Лискова уникальны для объектно-ориентированного программирования. Вот что я имел в виду под этим.
Мейсон Уилер
3
@ Мейсон: руководство заключается в том, чтобы «отдавать предпочтение композиции перед наследованием». Это никоим образом не означает не использовать и даже не избегать наследования. Все это говорит о том, что когда все остальные вещи равны, выбирай композицию. Но если вам нужно наследование, используйте его!
Джеффри Фауст
7

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

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

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

Кроме того, ответ Стивена Лоу. Действительно, действительно это.

Джек В.
источник
1
Мне нравится твоя аналогия
barrylloyd
2

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

Когда вы смотрите на класс, он делает больше, чем должен ( SRP )? Является ли это дублированием функциональности без необходимости ( СУХОЙ ) или чрезмерно заинтересован в свойствах или методах других классов ( Feature Envy )? Если класс нарушает все эти концепции (и, возможно, даже больше), он пытается стать классом Бога . Это целый ряд проблем, которые могут возникнуть при разработке программного обеспечения, но ни одна из которых не обязательно является проблемой наследования, но может быстро создать серьезные головные боли и хрупкие зависимости, где также применяется полиморфизм.

Корень проблемы, скорее всего, заключается не в недостаточном понимании наследования, а скорее в том, что он либо неудачен с точки зрения дизайна, либо, возможно, не распознает «запахи кода», относящиеся к классам, которые не придерживаются принципа единой ответственности . Полиморфизм и замещение Лискова не нужно отбрасывать в пользу композиции. Сам полиморфизм может быть применен, не полагаясь на наследование, все это довольно взаимодополняющие понятия. Если применять вдумчиво. Хитрость заключается в том, чтобы сохранить ваш код простым и чистым и не поддаваться чрезмерной озабоченности количеством классов, которые вам нужно создать для создания надежной конструкции системы.

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

S.Robins
источник
-1

Я полагаю, что настоящая цитата взята из «Эффективной Явы» Джошуа Блоха, где она называется одной из глав. Он утверждает, что наследование безопасно в пакете и там, где класс был специально разработан и задокументирован для расширения. Как утверждают другие, он утверждает, что наследование нарушает инкапсуляцию, поскольку подкласс зависит от деталей реализации его суперкласса. Это приводит, по его словам, к хрупкости.

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

Ванесса Уильямс
источник