Предпочитают состав над наследованием?

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

1350
задан Readonly 08 сент. '08 в 4:58
источник поделиться

34 ответов

  • 1
  • 2

Предпочитайте композицию над наследованием, поскольку она более податлива/легко модифицируется позже, но не использует метод compose-always. С составом легко менять поведение на лету с помощью Injection/Setters Dependency. Наследование более жесткое, так как большинство языков не позволяют вам получать более одного типа. Таким образом, гусь более или менее готовят, как только вы получаете от TypeA.

Мой кислотный тест для вышеуказанного:

  • Does TypeB хочет разоблачить полный интерфейс (все общедоступные методы не менее) типа A, чтобы TypeB можно было использовать там, где ожидается TypeA? Указывает Наследование.

например. Биплан Cessna откроет полный интерфейс самолета, если не больше. Это делает его пригодным для получения от самолета.

  • Ли TypeB хочет только часть/часть поведения, проявляемого TypeA? Указывает на необходимость Состав.

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

Обновление: Только что вернулось к моему ответу, и теперь кажется, что он неполный без конкретного упоминания Барбары Лисков Liskov Substitution Принцип в качестве теста на "Должен ли я наследовать этот тип?"

1014
ответ дан Gishu 10 сент. '08 в 6:04
источник поделиться

Подумайте, что сдерживание как имеет отношение. "Автомобиль" имеет "двигатель, человек" имеет "имя" и т.д.

Думайте о наследовании как о как о. "Автомобиль" - это "транспортное средство, человек" - "млекопитающее и т.д.".

Я не отношусь к этому подходу. Я взял это прямо из Второе издание кода завершено Стив Макконнелл, раздел 6.3.

349
ответ дан Nick Zalutskiy 08 сент. '08 в 5:09
источник поделиться

Если вы понимаете разницу, легче объяснить.

Процедурный код

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

Наследование

Это поощряет использование классов. Наследование является одним из трех принципов проектирования OO (наследование, полиморфизм, инкапсуляция).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

Это наследование на работе. "Сотрудник" является "Лицом или наследуется от Лица". Все отношения наследования - это отношения "есть-а". Сотрудник также затеняет свойство Title от Person, то есть Employee.Title вернет Title для Employee, а не Person.

Состав

Композиция предпочтительнее наследования. Проще говоря, вы бы:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

Состав обычно имеет отношение "имеет" или "использует". Здесь у класса Employee есть Person. Он не наследуется от Person, а вместо этого получает переданный ему объект Person, поэтому он имеет "Person".

Состав над наследованием

Теперь скажите, что вы хотите создать тип менеджера, в результате чего вы получите:

class Manager : Person, Employee {
   ...
}

Этот пример отлично работает, однако, что, если Person and Employee объявили Title? Должен ли Manager.Title вернуть "Менеджер операций" или "Г-н"? По составу эта двусмысленность лучше обрабатывается:

Class Manager {
   public Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Объект Manager состоит из Employee и Person. Поведение заголовка берется у сотрудника. Эта явная композиция устраняет двусмысленность между прочим, и вы столкнетесь с меньшим количеством ошибок.

175
ответ дан aleemb 21 мая '09 в 10:54
источник поделиться

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

Недостатки наследования:

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

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

91
ответ дан Galilyou 21 мая '09 в 11:34
источник поделиться

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

77
ответ дан Tim Howland 08 сент. '08 в 5:48
источник поделиться

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

Вы должны отдать предпочтение картофелю по кока-коле, когда хотите есть, и кока-кола на картофеле, когда вы хотите выпить.

Создание подкласса должно означать не просто удобный способ вызова методов суперкласса. Вы должны использовать наследование, если суперкласс класса "is-a" как структурно, так и функционально, когда его можно использовать в качестве суперкласса, и вы собираетесь его использовать. Если это не так - это не наследование, а что-то другое. Композиция - это когда ваши объекты состоят из другого или имеют какое-то отношение к ним.

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

64
ответ дан Pavel Feldman 09 мая '09 в 1:29
источник поделиться

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

, вероятно, является наихудшей формой связи, которую вы можете иметь

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

Статья, Наследование зла: эпическая ошибка DataAnnotationsModelBinder, проходит пример этого в С#. Он показывает использование наследования, когда композиция должна была использоваться и как ее можно было реорганизовать.

54
ответ дан Mike Valenty 23 янв. '10 в 22:55
источник поделиться

В Java или С# объект не может изменить свой тип после его создания.

Итак, если ваш объект должен появляться как другой объект или вести себя по-разному в зависимости от состояния объекта или условий, используйте Состав: см. State и Strategy Шаблоны проектирования.

Если объект должен быть одного типа, используйте Наследование или реализуйте интерфейсы.

37
ответ дан Sung Kim 08 сент. '08 в 5:25
источник поделиться

Лично я научился всегда предпочитать композицию над наследованием. Не существует проблем с программной проблемой, которую вы можете решить с наследованием, которую вы не можете решить с помощью композиции; хотя в некоторых случаях вам, возможно, придется использовать интерфейсы (Java) или протоколы (Obj-C). Поскольку С++ не знает ничего подобного, вам придется использовать абстрактные базовые классы, что означает, что вы не можете полностью избавиться от наследования в С++.

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

Однако есть недостатки композиции. Если вы вообще пропустите наследование и сосредоточьтесь только на композиции, вы заметите, что вам часто приходится писать пару лишних строк кода, которые не были необходимы, если вы использовали наследование. Вы также иногда вынуждены повторять себя, и это нарушает принцип СУХОЙ (DRY = Do not Repeat Yourself). Также состав часто требует делегирования, а метод просто вызывает другой метод другого объекта без другого кода, окружающего этот вызов. Такие "вызовы с двойным методом" (которые могут легко распространяться на вызовы с тройным или четверным методом и даже дальше) имеют гораздо худшую производительность, чем наследование, где вы просто наследуете метод вашего родителя. Вызов унаследованного метода может быть столь же быстрым, как вызов ненаследуемого, или он может быть немного медленнее, но обычно все же быстрее, чем два последовательных вызова метода.

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

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

28
ответ дан Mecki 31 янв. '13 в 22:34
источник поделиться

Не нашел удовлетворительного ответа здесь, поэтому я написал новый.

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

Есть два преимущества наследования: подтипирование и подклассы

  • Подтипирование означает соответствие сигнатуре типа (интерфейса), то есть набор API, и можно переопределить часть подписи для достижения политизма подтипов.

  • Подкласс означает неявное повторное использование реализаций методов.

С двумя преимуществами используются две разные цели для наследования: ориентированный на подтипирование и повторное использование кода.

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

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

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

  • Наследование обеспечивает простое повторное использование кода, если оно не переопределено, тогда как состав должен перекодировать каждый API, даже если это просто простая делеция делегирования.

  • Наследование предоставляет прямую открытую рекурсию через внутренний полиморфный сайт this, то есть вызов метода переопределения (или даже type) в другой членской функции, открытой или закрытой (хотя обескуражен). Открытая рекурсия может быть смоделирована с помощью композиции, но она требует дополнительных усилий и может не всегда быть жизнеспособной (?). Этот ответ на дублированный вопрос говорит о чем-то подобном.

  • Наследование предоставляет защищенные члены. Это нарушает инкапсуляцию родительского класса, а если используется подклассом, вводится другая зависимость между дочерним элементом и его родителем.

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

  • Композиция имеет преимущество комбинатор-ориентированное программирование, то есть работает таким образом, как составной шаблон.

  • Состав сразу следует за программированием на интерфейс.

  • Композиция имеет преимущество простого множественного наследования.

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

26
ответ дан lcn 14 сент. '15 в 8:22
источник поделиться

Мое общее правило: перед использованием наследования подумайте, имеет ли смысл смысл композиции.

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

Более полный и конкретный ответ от Тима Будро от Sun:

Общие проблемы использования наследования, как я вижу, это:

  • Невинные действия могут иметь неожиданные результаты. Классическим примером этого является призыв к переопределяемым методам из суперкласса конструктор, перед тем, как поля экземпляров подклассов были инициализируется. В идеальном мире никто никогда этого не сделает. Это не идеальный мир.
  • Он предлагает извращенные соблазны для подклассов делать предположения о порядке вызовов методов и т.д. - такие предположения, как правило, не быть стабильным, если суперкласс может развиваться со временем. См. Также мой тостер и аналог кофеварки.
  • Классы становятся тяжелее - вы не всегда знаете, что делает ваш суперкласс в своем конструкторе, или сколько памяти он собирается использовать. Таким образом, построение невинного потенциального легкого объекта может быть намного дороже, чем вы думаете, и это может измениться с течением времени, если суперкласс эволюционирует
  • Он поощряет взрыв подклассов. Classloading затраты времени, больше классов стоит памяти. Это может быть не проблема, пока вы не имея дело с приложением в масштабе NetBeans, но там у нас были реальные проблемы с, например, медленными меню, потому что первый дисплей из меню запускается массовая загрузка классов. Мы исправили это, перейдя к более декларативный синтаксис и другие методы, но это требует времени исправить также.
  • Это затрудняет изменение вещей позже - если вы сделали класс общедоступным, замена суперкласса будет разбивать подклассы - это выбор, который, после того, как вы сделали код общедоступным, вы состоите в браке к. Поэтому, если вы не изменяете реальную функциональность суперкласса, вы получите гораздо больше свободы, чтобы изменить вещи позже, если вы а не расширять то, что вам нужно. Возьмем, к примеру, подклассификация JPanel - это обычно неправильно; и если подкласс где-то публично, вы никогда не получите шанс вернуться к этому решению. Если он доступен как JComponent getThePanel(), вы все равно можете это сделать (подсказка: выставлять модели для компонентов внутри вашего API).
  • Иерархии объектов не масштабируются (или их масштабирование намного сложнее, чем планирование вперед) - это классическое "слишком много слоев", проблема. Я расскажу об этом ниже, и как шаблон AskTheOracle может (хотя это может оскорбить пуристов ООП).

...

Мое занятие, что делать, если вы разрешаете наследование, которое вы можете взять с солью:

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

...

все это меньше относится к небольшим проектам, чем к крупным, и меньше для частных классов, чем для публичных

19
ответ дан Peter Tseng 21 мая '12 в 4:59
источник поделиться

Наследование очень мощное, но вы не можете его заставить (см. проблема с круглым эллипсом). Если вы действительно не можете быть полностью уверены в истинном соотношении подтипов "is-a", тогда лучше всего пойти с композицией.

17
ответ дан yukondude 08 сент. '08 в 5:29
источник поделиться

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

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

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

Я бы предложил использовать компоновку по умолчанию. Он более модульный и дает преимущество позднего связывания (вы можете динамически изменять компонент). Также проще тестировать вещи по отдельности. И если вам нужно использовать метод из класса, вы не должны быть в определенной форме (принцип замены Лискова).

14
ответ дан egaga 21 мая '09 в 11:11
источник поделиться

Вам нужно взглянуть на Принцип замены Лискова у дяди Боба SOLID принципы дизайна класса.:)

13
ответ дан nabeelfarid 05 окт. '10 в 14:57
источник поделиться

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

Class Aircraft extends Engine{
  var wings;
}

Теперь ваш самолет может начать с закрепленных крыльев и менять их на поворотные крылья на лету. По существу, двигатель с крыльями. Но что, если я захочу изменить двигатель "на лету"?

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

Class Aircraft {
  var wings;
  var engine;
}

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

11
ответ дан dharm0us 29 нояб. '10 в 13:54
источник поделиться

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

Если между двумя объектами не существует иерархии реального мира, не используйте наследование, а вместо этого используйте композицию. Композиция представляет отношения "HAS".

например. Автомобиль HAS A Колеса, тело, двигатель и т.д. Но если вы наследуете здесь, он становится CAR IS A Wheel - что неверно.

Дополнительные пояснения см. в предпочитают состав над наследованием

9
ответ дан Ranganatha 21 мая '15 в 10:52
источник поделиться

Почему предпочитают композицию над наследованием?

См. Другие ответы.

Когда следует выбирать наследование над составом?

Всякий раз, когда предложение "Бар - это Foo, а Bar может делать все, что может сделать Foo", имеет смысл.

Обычная мудрость гласит, что если предложение "Бар является Foo" имеет смысл, то это хороший намек на то, что выбор наследования является подходящим. Например, собака - это животное, поэтому наличие наследования у собак из Animal - это, вероятно, хороший дизайн.

К сожалению, этот простой тест "is-a" не является надежным. Проблема Circle-Ellipse - отличный контрастный пример: даже если круг является эллипсом, это плохая идея, чтобы класс Circle наследовался от Ellipse, потому что есть вещи, которые могут делать эллипсы, но круги не могут. Например, эллипсы могут растягиваться, но круги не могут. Таким образом, хотя вы можете иметь ellipse.stretch(), вы не можете иметь circle.stretch().

Вот почему лучший тест: "Бар - это Foo, а Bar может делать все, что может сделать Foo". Это действительно означает, что Foo можно использовать полиморфно. Тест "is-a" является лишь необходимым условием полиморфного использования и обычно означает, что все геттеры Foo имеют смысл в Bar. Дополнительное тестирование "can-do-everything" означает, что все сеттеры Foo также имеют смысл в Bar. Этот дополнительный тест обычно терпит неудачу, когда класс Bar "is-a" Foo, но добавляет к нему некоторые ограничения, и в этом случае вы не должны использовать наследование, потому что Foo не может использоваться полиморфно. Другими словами, наследование заключается не в совместном использовании свойств, а в обмене функциональными возможностями. Производные классы должны расширять функциональность базовых классов, а не ограничивать их.

Это эквивалентно Принципу замещения Лискова:

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

9
ответ дан Boris 04 июня '16 в 1:23
источник поделиться

Если вы хотите "скопировать" /выдать API базового класса, вы используете наследование. Когда вы хотите только "скопировать" функциональность, используйте делегирование.

Один из примеров: вы хотите создать Stack из списка. Стек имеет только pop, push и peek. Вы не должны использовать наследование, учитывая, что вам не нужны функции push_back, push_front, removeAt и др. В стеке.

6
ответ дан Anzurio 07 мая '09 в 4:43
источник поделиться

Эти два способа могут жить вместе прекрасно и фактически поддерживать друг друга.

Композиция просто играет на модуле: вы создаете интерфейс, похожий на родительский, создаете новый объект и делегируете его. Если эти объекты не должны знать друг друга, это вполне безопасный и простой в использовании состав. Здесь так много возможностей.

Однако, если родительскому классу по какой-то причине требуется доступ к функциям, предоставляемым "дочерним классом" для неопытного программиста, может показаться, что это отличное место для использования наследования. Родительский класс может просто называть его собственным абстрактным "foo()" , который перезаписывается подклассом, а затем он может дать значение абстрактной базе.

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

Почему?

Поскольку наследование - это плохой способ перемещения информации.

Здесь композиция имеет реальное преимущество: отношения могут быть отменены: "родительский класс" или "абстрактный рабочий" могут агрегировать любые конкретные "дочерние" объекты, реализующие определенный интерфейс + , любой ребенок может быть установлен в любом другом тип родителя, который принимает его тип. И может быть любое количество объектов, например MergeSort или QuickSort могут сортировать любой список объектов, реализующих абстрактный Compare -interface. Или иначе: любая группа объектов, которые реализуют "foo()" и другую группу объектов, которые могут использовать объекты, имеющие "foo()" , могут играть вместе.

Я могу думать о трех реальных причинах использования наследования:

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

Если они верны, то, вероятно, необходимо использовать наследование.

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

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

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

6
ответ дан Tero Tolonen 16 апр. '13 в 23:15
источник поделиться

Помимо того, что a/имеет соображения, нужно также учитывать "глубину" наследования, которое должен пройти ваш объект. Все, что находится за пределами пяти или шести уровней наследования, может вызвать непредвиденные проблемы с литьем и боксом/распаковкой, и в таких случаях может быть разумным составить свой объект вместо этого.

5
ответ дан Jon Limjap 08 сент. '08 в 6:00
источник поделиться

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

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

5
ответ дан Y.S. 04 июня '14 в 14:36
источник поделиться

Если у вас есть отношение is-a между двумя классами (например, собака - собака), вы идете для наследования.

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

5
ответ дан Amir Aslam 30 дек. '14 в 4:57
источник поделиться

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

4
ответ дан Shelby Moore III 02 дек. '11 в 10:40
источник поделиться

Я согласен с @Pavel, когда он говорит, есть места для композиции и есть места для наследования.

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

  • Является ли ваша класс частью структуры, которая приносит пользу от полиморфизма? Например, если у вас есть класс Shape, который объявляет метод под названием draw(), то нам явно нужны классы Circle и Square для подклассов Shape, так что их клиентские классы будут зависеть от Shape, а не от определенных подклассов.
  • Требуется ли вашему классу повторное использование любых взаимодействий высокого уровня, определенных в другом классе? шаблонный метод шаблона проектирования невозможно реализовать без наследования. Я считаю, что все расширяемые структуры используют этот шаблон.

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

4
ответ дан Parag 07 дек. '11 в 13:40
источник поделиться

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

3
ответ дан Evrhet Milam 08 сент. '08 в 5:14
источник поделиться

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

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

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

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

  • Мы обнаруживаем, что есть некоторые другие функции, которые мы хотим, чтобы наши классы имели. Если способ добавления функциональности к классам осуществляется через наследование, мы должны решить - добавим ли мы его в существующий базовый класс, хотя не каждый класс, который наследует от него, нуждается в этой функции? Создаем ли мы еще один базовый класс? Но как насчет классов, которые уже наследуются от другого базового класса?
  • Мы обнаруживаем, что только для одного из классов, наследуемых от нашего базового класса, мы хотим, чтобы базовый класс вел себя немного по-другому. Итак, теперь мы возвращаемся и возимся с нашим базовым классом, возможно, добавляем некоторые виртуальные методы или, что еще хуже, некоторый код, который говорит: "Если я унаследован тип A, сделайте это, но если я унаследован от типа B, сделайте это". Это плохо по многим причинам. Во-первых, каждый раз, когда мы меняем базовый класс, мы эффективно меняем каждый унаследованный класс. Таким образом, мы действительно меняем класс A, B, C и D, потому что нам нужно немного другое поведение в классе A. Как бы мы ни старались, мы можем сломать один из этих классов по причинам, которые не имеют никакого отношения к тем классы.
  • Мы могли бы знать, почему мы решили заставить все эти классы наследовать друг от друга, но это может не (вероятно, не будет) иметь смысл для кого-то, кто должен поддерживать наш код. Мы могли бы заставить их сделать трудный выбор - я делаю что-то действительно уродливое и беспорядочное, чтобы внести необходимые изменения (см. Предыдущую маркерную точку) или просто переписать кучу этого.

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

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

Антидотом является принцип единой ответственности. Подумайте об этом как об ограничении. Мой класс должен сделать одно. Я должен быть в состоянии дать моему классу имя, которое каким-то образом описывает это одно. (Есть исключения во всем, но абсолютные правила иногда лучше, когда мы учимся.) Из этого следует, что я не могу написать базовый класс под названием ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses. Независимо от того, какую функциональность мне нужно, должен быть в своем классе, а затем другие классы, которые нуждаются в этой функции, могут зависеть от этого класса, а не наследовать от него.

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

2
ответ дан Scott Hannen 13 мая '16 в 23:24
источник поделиться

Состав v/s Наследование - широкий предмет. Нет никакого реального ответа на то, что лучше, поскольку я думаю, что все зависит от дизайна системы.

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

Если тип отношения - это отношение "IS-A", тогда наследование лучше подходит. иначе тип отношения - отношение "HAS-A", тогда композиция будет лучше приближаться.

Его полностью зависит от отношения сущности.

2
ответ дан Shami Qureshi 18 марта '14 в 12:39
источник поделиться

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

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

Когда мы говорим, что число 5 имеет тип integer, мы утверждаем, что 5 принадлежит множеству возможных значений (в качестве примера см. возможные значения для примитивных типов Java). Мы также заявляем, что существует допустимый набор методов, которые я могу выполнить для значения, такого как сложение и вычитание. И, наконец, мы заявляем, что существует набор свойств, которые всегда выполняются, например, если я добавлю значения 3 и 5, я получу 8 в результате.

Чтобы привести еще один пример, подумайте об абстрактных типах данных, наборе целых чисел и списке целых чисел, значения, которые они могут удерживать, ограничены целыми числами. Они поддерживают набор методов, таких как add (newValue) и size(). И они оба имеют разные свойства (инвариант класса), Sets не позволяет дублировать, в то время как List разрешает дубликаты (конечно, есть другие свойства, которые оба они удовлетворяют).

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

class List {
  data = new Array();

  Integer size() {
    return data.length;
  }

  add(Integer anInteger) {
    data[data.length] = anInteger;
  }
}

Затем я пишу Набор целых чисел как подкласс списка целых чисел:

class Set, inheriting from: List {
  add(Integer anInteger) {
     if (data.notContains(anInteger)) {
       super.add(anInteger);
     }
  }
}

Наш класс целых чисел является подклассом List of Integer, но не является подтипом, поскольку он не удовлетворяет всем функциям класса List. Значения и сигнатура методов выполняются, но свойства не являются. Поведение метода add (Integer) явно изменилось, не сохраняя свойства родительского типа. Подумайте, с точки зрения клиента ваших классов. Они могут получить набор целых чисел, где ожидается список целых чисел. Клиент может захотеть добавить значение и получить добавленное значение в список, даже если это значение уже существует в списке. Но она не получит такое поведение, если значение существует. Большой сюрприз для нее!

Это классический пример ненадлежащего использования наследования. Используйте композицию в этом случае.

(фрагмент из: правильно использует наследование).

2
ответ дан Enrique Molinari 31 авг. '13 в 16:41
источник поделиться

Несмотря на то, что композиция предпочтительнее, я хотел бы подчеркнуть достоинства Inheritance и cons of Composition.

Плюсы наследования:

  • Он устанавливает логическое отношение IS A. Если автомобиль и грузовик являются двумя типами транспортных средств (базовый класс), базовый класс дочернего класса IS A.

    то есть.

    Автомобиль - это автомобиль

    Грузовик - это автомобиль

  • С наследованием вы можете определить/изменить/расширить возможности

    • Базовый класс не обеспечивает реализацию, а подкласс должен переопределить полный метод (abstract) = > Вы можете реализовать контракт
    • Базовый класс предоставляет реализацию по умолчанию, а подкласс может изменить поведение = > Вы можете переопределить контракт
    • Подкласс добавляет расширение к реализации базового класса, вызывая super.methodName() как первый оператор = > Вы можете продлить контракт
    • Базовый класс определяет структуру алгоритма, а подкласс будет переопределять часть алгоритма = > Вы можете реализовать Template_method без изменения скелета базового класса

Недостатки композиции:

  • В наследовании подкласс может напрямую вызывать метод базового класса, даже если он не реализует метод базового класса из-за отношения IS A. Если вы используете композицию, вам нужно добавить методы в класс контейнера, чтобы выставить закрытый API классов.

например. Если автомобиль содержит автомобиль, и если вам нужно получить цену на автомобиль, который был определен в автомобиле, ваш код будет таким, как

class Vehicle{
     protected double getPrice(){
          // return price
     }
} 

class Car{
     Vehicle vehicle;
     protected double getPrice(){
          return vehicle.getPrice();
     }
} 
1
ответ дан Ravindra babu 22 сент. '16 в 12:31
источник поделиться

Как и многие люди, я сначала начну с проверки - существует ли связь "есть-а". Если он существует, я обычно проверяю следующее:

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

Например: 1. Бухгалтер - сотрудник. Но я использую не наследование, потому что объект Employee может быть создан.

Например, 2. Книга - это SellingItem. Нельзя создавать экземпляр SellingItem - это абстрактное понятие. Следовательно, я буду использовать inheritacne. SellingItem является абстрактным базовым классом (или интерфейсом на С#)

Что вы думаете об этом подходе?

Кроме того, я поддерживаю @anon ответ в Зачем использовать наследование вообще?

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

@MatthieuM. говорится в https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448

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

(для полиморфизма)

(для повторного использования кода)

ССЫЛКА

1
ответ дан Lijo 01 авг. '12 в 14:15
источник поделиться
  • 1
  • 2

Другие вопросы по меткам