Подтвердить что ты не робот

Наследование или состав: полагайтесь на "есть-а" и "есть-а"?

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

Это всегда правильно?

Спасибо.

4b9b3361

Ответ 1

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

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

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

void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}

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

Ответ 2

Да и нет.

Линия может быть размытой. Этому не помогли некоторые довольно ужасные примеры программирования OO с первых дней OO, таких как: Менеджер - это Сотрудник - Человек.

Вещь, которую вы должны помнить о наследовании: наследование прерывает инкапсуляцию. Наследование - это деталь реализации. Там все виды написаны на эту тему.

Самый простой способ подвести итог:

Предпочитаемая композиция.

Это не значит использовать его для полного исключения наследования. Это просто означает, что наследование является резервным.

Ответ 3

Если отношение "is-a" - использовать наследование, и если отношение "has-a" - использовать композицию. Всегда ли это правильно?

В некотором смысле, да. Но вы должны быть осторожны, чтобы не вводить ненужные, искусственные отношения "есть-а".

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

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

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

Ответ 4

Я не думаю, что это так просто, как "is-a" vs "has-a", как сказал cletus, линия может сильно размываться. Это не так, как два человека всегда приходили к одному и тому же выводу, учитывая это как руководство.

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

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

Ответ 5

Наследование не всегда означает, что существует отношение "есть-а", а отсутствие одного не всегда означает, что нет.

Примеры:

  • Если вы используете защищенный или закрытый наследование, то вы теряете внешние заменяемости, т.е. до кто-либо за пределами вашей иерархии заинтересованный. Производный не является типом База.
  • Если вы переопределите базовый виртуальный элемент и измените поведение, наблюдаемое от кого-либо, ссылающегося на базу, от исходной реализации Base, то вы эффективно сломали подменяемость. Даже несмотря на то, что у вас есть наследственное наследство, наследственность Derived is-not-the Base.
  • Ваш класс иногда может быть подменим для другого без каких-либо отношений наследования на работе. Например, если вы должны использовать шаблоны и идентичные интерфейсы для соответствующих функций-членов-членов, то "Ellipse const &" где эллипс, как представляется, является абсолютно круговым, может быть подменимым для "Circle const &".

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

Ответ 6

Нетдел > Да. Если отношение "has-a", используйте композицию. (Добавлено: исходная версия вопроса говорит "использовать наследование" для обоих - простая опечатка, но коррекция меняет первое слово моего ответа от "Нет" до "Да".)

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

Ответ 7

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

Первый говорит: "Мой класс похож на этот другой класс. Я просто напишу код для дельта между ними и повторно использую другую реализацию из другого класса, насколько я могу".

Второй зависит от абстрактных базовых классов и представляет собой список методов, которые реализует класс promises.

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

Ответ 8

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

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

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

Ответ 9

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

Это заставляет меня писать меньше кода, который по своей сути проще поддерживать.