Наследование или состав: Положитесь на "есть-а" и "есть-а"? - PullRequest
28 голосов
/ 17 января 2009

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

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

Спасибо.

Ответы [ 9 ]

35 голосов
/ 17 января 2009

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

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

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

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

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

8 голосов
/ 17 января 2009

Да и нет.

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

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

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

Предпочитают состав.

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

5 голосов
/ 17 января 2009

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

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

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

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

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

3 голосов
/ 17 января 2009

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

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

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

2 голосов
/ 17 января 2009

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

Примеры:

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

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

1 голос
/ 18 января 2009

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

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

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

1 голос
/ 17 января 2009

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

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

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

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

1 голос
/ 17 января 2009

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

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

0 голосов
/ 17 января 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...