На главную страницу
Форум txt.version   



Статья :: 34. Предпочитайте композицию наследованию : Герб Саттер

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

Резюме

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

Обсуждение

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

Известно, что наследование — практически самое сильное взаимоотношение, которое можно выразить средствами С++; сильнее его только отношение дружбы, и пользоваться им следует только при отсутствии функционально эквивалентной более слабой альтернативы. Если вы можете выразить отношения классов с использованием только лишь композиции, следует использовать этот способ.

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

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

• Большая гибкость без влияния на вызывающий код: закрытые члены-данные находятся под полным вашим контролем. Вы можете хранить их по значению, посредством (интеллектуального) указателя или с использованием идиомы Pimpl (см. рекомендацию 43), при этом переход от одного способа хранения к другому никак не влияет на код вызывающей функции: все, что при этом меняется, — это реализация функций-членов класса, использующих упомянутые члены-данные. Если вы решите, что вам требуется иная функциональность, вы можете легко изменить тип или способ хранения члена при полной сохранности открытого интерфейса. Если же вы начнете с открытого наследования, то скорее всего вы не сможете легко и просто изменить ваш базовый класс в случае необходимости (см. рекомендацию 37).

• Большая обособленность в процессе компиляции, уменьшение времени компиляции. Хранение объекта посредством указателя (предпочтительно — интеллектуального указателя), а не в виде непосредственного члена или базового класса позволяет также снизить зависимости заголовочных файлов, поскольку объявление указателя на объект не требует полного определения класса этого объекта. Наследование, напротив, всегда требует видимости полного определения базового класса. Распространенная методика состоит в том, чтобы собрать все закрытые члены воедино посредством одного непрозрачного указателя (идиома Pimpl, см. рекомендацию 43).

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

• Большая применимость. Не все классы проектируются с учетом того, что они будут выступать в роли базовых (см. рекомендацию 35). Однако большинство классов вполне могут справиться с ролью члена.

• Большая надежность и безопасность. Более сильное связывание путем наследования затрудняет написание безопасного в смысле ошибок кода (см. [Sutter02] §23).

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

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

Исключения

Используйте открытое наследование для моделирования заменимости (см. рекомендацию 37).

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

• Если вам требуется перекрытие виртуальной функции.

• Если вам нужен доступ к защищенному члену.

• Если вам надо создавать объект до используемого, а уничтожать — после, сделайте его базовым классом.

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

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

• Если вам требуется управляемый полиморфизм, т.е. отношение заменимости, которое должно быть видимо только определенному коду (посредством дружбы).

Ссылки

[Cargill92] pp. 49-65, 101-105 • [Cline99] §5.9-10, 8.11-12, 37.04 • [Dewhurst03] §95 • [Lakos96] §1.7, §6.3.1 • [McConnell93] §5 • [Meyers97] §40 • [Stroustrup00] §24.2-3 • [Sutter00] §22-24, §26-30 • [Sutter02] §23




34. Предпочитайте композицию наследованию : Герб Саттер

страницы в данном разделе 
j0.html 0. Не мелочитесь, или Что не следует стандартизировать : Герб Саттер
1. Компилируйте без замечаний при максимальном уровне предупреждений : Герб Саттер 2. Используйте автоматические системы сборки программ : Герб Саттер
3. Используйте систему контроля версий : Герб Саттер 4. Одна голова хорошо, а две — лучше : Герб Саттер
0. Не мелочитесь, или Что не следует стандартизировать : Герб Саттер 1. Компилируйте без замечаний при максимальном уровне предупреждений : Герб Саттер
2. Используйте автоматические системы сборки программ : Герб Саттер 3. Используйте систему контроля версий : Герб Саттер
4. Одна голова хорошо, а две — лучше : Герб Саттер Стиль проектирования : Герб Саттер
5. Один объект — одна задача : Герб Саттер 6. Главное — корректность, простота и ясность : Герб Саттер
7. Кодирование с учетом масштабируемости : Герб Саттер 8. Не оптимизируйте преждевременно : Герб Саттер
9. Не пессимизируйте преждевременно : Герб Саттер 10. Минимизируйте глобальные и совместно используемые данные : Герб Саттер
11. Сокрытие информации : Герб Саттер 12. Кодирование параллельных вычислений : Герб Саттер
13. Ресурсы должны быть во владении объектов : Герб Саттер продолжение 21
5. Один объект — одна задача : Герб Саттер 6. Главное — корректность, простота и ясность : Герб Саттер
7. Кодирование с учетом масштабируемости : Герб Саттер 8. Не оптимизируйте преждевременно : Герб Саттер
9. Не пессимизируйте преждевременно : Герб Саттер 10. Минимизируйте глобальные и совместно используемые данные : Герб Саттер
11. Сокрытие информации : Герб Саттер 12. Кодирование параллельных вычислений : Герб Саттер
13. Ресурсы должны быть во владении объектов : Герб Саттер 14. Предпочитайте ошибки компиляции и компоновки ошибкам времени выполнения : Герб Саттер
15. Активно используйте const : Герб Саттер 16. Избегайте макросов : Герб Саттер
17. Избегайте магических чисел : Герб Саттер 18. Объявляйте переменные как можно локальнее : Герб Саттер
19. Всегда инициализируйте переменные : Герб Саттер 20. Избегайте длинных функций и глубокой вложенности : Герб Саттер
21. Избегайте зависимостей инициализаций между единицами компиляции : Герб Саттер 22. Минимизируйте зависимости определений и избегайте циклических зависимостей : Герб Саттер
23. Делайте заголовочные файлы самодостаточными : Герб Саттер 24. Используйте только внутреннюю, но не внешнюю защиту директивы #include : Герб Саттер
14. Предпочитайте ошибки компиляции и компоновки ошибкам времени выполнения : Герб Саттер 15. Активно используйте const : Герб Саттер
16. Избегайте макросов : Герб Саттер 17. Избегайте магических чисел : Герб Саттер
18. Объявляйте переменные как можно локальнее : Герб Саттер 19. Всегда инициализируйте переменные : Герб Саттер
20. Избегайте длинных функций и глубокой вложенности : Герб Саттер 21. Избегайте зависимостей инициализаций между единицами компиляции : Герб Саттер
22. Минимизируйте зависимости определений и избегайте циклических зависимостей : Герб Саттер 23. Делайте заголовочные файлы самодостаточными : Герб Саттер
24. Используйте только внутреннюю, но не внешнюю защиту директивы #include : Герб Саттер 25. Передача параметров по значению, (интеллектуальному) указателю или ссылке : Герб Саттер
26. Сохраняйте естественную семантику перегруженных операторов : Герб Саттер j55.html
28. Предпочитайте канонический вид ++ и --, и вызов префиксных операторов : Герб Саттер 29. Используйте перегрузку, чтобы избежать неявного преобразования типов : Герб Саттер
30. Избегайте перегрузки &&, || и , (запятой) : Герб Саттер 31. Не пишите код, который зависит от порядка вычислений аргументов функции : Герб Саттер
25. Передача параметров по значению, (интеллектуальному) указателю или ссылке : Герб Саттер 26. Сохраняйте естественную семантику перегруженных операторов : Герб Саттер
j62.html 28. Предпочитайте канонический вид ++ и --, и вызов префиксных операторов : Герб Саттер
29. Используйте перегрузку, чтобы избежать неявного преобразования типов : Герб Саттер 30. Избегайте перегрузки &&, || и , (запятой) : Герб Саттер
31. Не пишите код, который зависит от порядка вычислений аргументов функции : Герб Саттер 32. Ясно представляйте, какой вид класса вы создаете : Герб Саттер
33. Предпочитайте минимальные классы монолитным : Герб Саттер 34. Предпочитайте композицию наследованию : Герб Саттер
35. Избегайте наследования от классов, которые не спроектированы для этой цели : Герб Саттер 36. Предпочитайте предоставление абстрактных интерфейсов : Герб Саттер
j72.html 38. Практикуйте безопасное перекрытие : Герб Саттер
39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными : Герб Саттер 40. Избегайте возможностей неявного преобразования типов : Герб Саттер
41. Делайте данные-члены закрытыми (кроме случая агрегатов в стиле структур С) : Герб Саттер 42. Не допускайте вмешательства во внутренние дела : Герб Саттер
43. Разумно пользуйтесь идиомой Pimpl : Герб Саттер 44. Предпочитайте функции, которые не являются ни членами, ни друзьями : Герб Саттер
45. new и delete всегда должны разрабатываться вместе : Герб Саттер j81.html
32. Ясно представляйте, какой вид класса вы создаете : Герб Саттер 33. Предпочитайте минимальные классы монолитным : Герб Саттер
34. Предпочитайте композицию наследованию : Герб Саттер 35. Избегайте наследования от классов, которые не спроектированы для этой цели : Герб Саттер
36. Предпочитайте предоставление абстрактных интерфейсов : Герб Саттер j87.html
38. Практикуйте безопасное перекрытие : Герб Саттер 39. Виртуальные функции стоит делать неоткрытыми, а открытые — невиртуальными : Герб Саттер
40. Избегайте возможностей неявного преобразования типов : Герб Саттер 41. Делайте данные-члены закрытыми (кроме случая агрегатов в стиле структур С) : Герб Саттер
42. Не допускайте вмешательства во внутренние дела : Герб Саттер 43. Разумно пользуйтесь идиомой Pimpl : Герб Саттер
44. Предпочитайте функции, которые не являются ни членами, ни друзьями : Герб Саттер 45. new и delete всегда должны разрабатываться вместе : Герб Саттер
j96.html 47. Определяйте и инициализируйте переменные-члены в одном порядке : Герб Саттер
48. В конструкторах предпочитайте инициализацию присваиванию : Герб Саттер 49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах : Герб Саттер
j100.html 51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются : Герб Саттер
52. Копируйте и ликвидируйте согласованно : Герб Саттер 53. Явно разрешайте или запрещайте копирование : Герб Саттер
j104.html 55. Предпочитайте канонический вид присваивания : Герб Саттер
56. Обеспечьте бессбойную функцию обмена : Герб Саттер 47. Определяйте и инициализируйте переменные-члены в одном порядке : Герб Саттер
48. В конструкторах предпочитайте инициализацию присваиванию : Герб Саттер 49. Избегайте вызовов виртуальных функций в конструкторах и деструкторах : Герб Саттер
j110.html 51. Деструкторы, функции освобождения ресурсов и обмена не ошибаются : Герб Саттер
52. Копируйте и ликвидируйте согласованно : Герб Саттер 53. Явно разрешайте или запрещайте копирование : Герб Саттер
j114.html 55. Предпочитайте канонический вид присваивания : Герб Саттер
56. Обеспечьте бессбойную функцию обмена : Герб Саттер Пространства имен и модули : Герб Саттер
57. Храните типы и их свободный интерфейс в одном пространстве имен : Герб Саттер j119.html
j120.html 60. Избегайте выделения и освобождения памяти в разных модулях : Герб Саттер
61. Не определяйте в заголовочном файле объекты со связыванием : Герб Саттер 62. Не позволяйте исключениям пересекать границы модулей : Герб Саттер
63. Используйте достаточно переносимые типы в интерфейсах модулей : Герб Саттер продолжение 125
57. Храните типы и их свободный интерфейс в одном пространстве имен : Герб Саттер j127.html
j128.html 60. Избегайте выделения и освобождения памяти в разных модулях : Герб Саттер
61. Не определяйте в заголовочном файле объекты со связыванием : Герб Саттер 62. Не позволяйте исключениям пересекать границы модулей : Герб Саттер
63. Используйте достаточно переносимые типы в интерфейсах модулей : Герб Саттер 64. Разумно сочетайте статический и динамический полиморфизм : Герб Саттер
65. Выполняйте настройку явно и преднамеренно : Герб Саттер 66. Не специализируйте шаблоны функций : Герб Саттер
67. Пишите максимально обобщенный код : Герб Саттер 64. Разумно сочетайте статический и динамический полиморфизм : Герб Саттер
65. Выполняйте настройку явно и преднамеренно : Герб Саттер 66. Не специализируйте шаблоны функций : Герб Саттер
67. Пишите максимально обобщенный код : Герб Саттер 68. Широко применяйте assert для документирования внутренних допущений и инвариантов : Герб Саттер
69. Определите разумную стратегию обработки ошибок и строго ей следуйте : Герб Саттер 70. Отличайте ошибки от ситуаций, не являющихся ошибками : Герб Саттер
71. Проектируйте и пишите безопасный в отношении ошибок код : Герб Саттер 72. Для уведомления об ошибках следует использовать исключения : Герб Саттер
73. Генерируйте исключения по значению, перехватывайте — по ссылке : Герб Саттер 74. Уведомляйте об ошибках, обрабатывайте и преобразовывайте их там, где следует : Герб Саттер
75. Избегайте спецификаций исключений : Герб Саттер 68. Широко применяйте assert для документирования внутренних допущений и инвариантов : Герб Саттер
69. Определите разумную стратегию обработки ошибок и строго ей следуйте : Герб Саттер 70. Отличайте ошибки от ситуаций, не являющихся ошибками : Герб Саттер
71. Проектируйте и пишите безопасный в отношении ошибок код : Герб Саттер 72. Для уведомления об ошибках следует использовать исключения : Герб Саттер
73. Генерируйте исключения по значению, перехватывайте — по ссылке : Герб Саттер 74. Уведомляйте об ошибках, обрабатывайте и преобразовывайте их там, где следует : Герб Саттер
75. Избегайте спецификаций исключений : Герб Саттер j157.html
77. Вместо массивов используйте vector и string : Герб Саттер 78. Используйте vector (и string::c_str) для обмена данными с API на других языках : Герб Саттер
79. Храните в контейнерах только значения или интеллектуальные указатели : Герб Саттер 80. Предпочитайте push_back другим способам расширения последовательности : Герб Саттер
81. Предпочитайте операции с диапазонами операциям с отдельными элементами : Герб Саттер j163.html
j164.html 77. Вместо массивов используйте vector и string : Герб Саттер
78. Используйте vector (и string::c_str) для обмена данными с API на других языках : Герб Саттер 79. Храните в контейнерах только значения или интеллектуальные указатели : Герб Саттер
80. Предпочитайте push_back другим способам расширения последовательности : Герб Саттер 81. Предпочитайте операции с диапазонами операциям с отдельными элементами : Герб Саттер
j170.html 83. Используйте отладочную реализацию STL : Герб Саттер
84. Предпочитайте вызовы алгоритмов самостоятельно разрабатываемым циклам : Герб Саттер 85. Пользуйтесь правильным алгоритмом поиска : Герб Саттер
86. Пользуйтесь правильным алгоритмом сортировки : Герб Саттер 87. Делайте предикаты чистыми функциями : Герб Саттер
j176.html 89. Корректно пишите функциональные объекты : Герб Саттер
83. Используйте отладочную реализацию STL : Герб Саттер 84. Предпочитайте вызовы алгоритмов самостоятельно разрабатываемым циклам : Герб Саттер
85. Пользуйтесь правильным алгоритмом поиска : Герб Саттер 86. Пользуйтесь правильным алгоритмом сортировки : Герб Саттер
87. Делайте предикаты чистыми функциями : Герб Саттер j183.html
89. Корректно пишите функциональные объекты : Герб Саттер 90. Избегайте явного выбора типов — используйте полиморфизм : Герб Саттер
91. Работайте с типами, а не с представлениями : Герб Саттер 92. Избегайте reinterpret_cast : Герб Саттер
93. Избегайте применения static_cast к указателям : Герб Саттер 94. Избегайте преобразований, отменяющих const : Герб Саттер
95. Не используйте преобразование типов в стиле С : Герб Саттер 96. Не применяйте memcpy или memcmp к не-POD типам : Герб Саттер
97. Не используйте объединения для преобразований : Герб Саттер 98. Не используйте неизвестные аргументы (троеточия) : Герб Саттер
99. Не используйте недействительные объекты и небезопасные функции : Герб Саттер 100. Не рассматривайте массивы полиморфно : Герб Саттер
90. Избегайте явного выбора типов — используйте полиморфизм : Герб Саттер 91. Работайте с типами, а не с представлениями : Герб Саттер
92. Избегайте reinterpret_cast : Герб Саттер 93. Избегайте применения static_cast к указателям : Герб Саттер
94. Избегайте преобразований, отменяющих const : Герб Саттер 95. Не используйте преобразование типов в стиле С : Герб Саттер
96. Не применяйте memcpy или memcmp к не-POD типам : Герб Саттер 97. Не используйте объединения для преобразований : Герб Саттер
98. Не используйте неизвестные аргументы (троеточия) : Герб Саттер 99. Не используйте недействительные объекты и небезопасные функции : Герб Саттер
100. Не рассматривайте массивы полиморфно : Герб Саттер Список литературы : Герб Саттер
Резюме из резюме : Герб Саттер notes.html

Разделы
Околокомпьютерная литература (375)
Программирование (102)
Программы (75)
ОС и Сети (49)
Интернет (29)
Аппаратное обеспечение (16)
Базы данных (6)
Flutter
React Native
Xamarin