Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 194
Текст из файла (страница 194)
Используйте агрегацию для отражения отношения 78аз-а"; 924.3.4. 15. Для отражения отношения включения используйте поля данных с типом включаемого объекта, а не с типом указателя на этот объект„924.3.3, 924.3.4. 16. Старайтесь делать отношения использования ясными и понятными, нециклическими и минимальными; 824.3.5.
17. Определяйте инварианты для всех классов; 924.3.7.!. 18. Предусловия, постусловия и иные утверждения выражайте с помощью утверждений (возможно, с помощью Аххсгго ); 924.3.7.2. 19. Определяйте интерфейсы, раскрывая минимум необходимой информации; 924.4. Глава 24. Проектирование и программирование 20. Минимизируйте зависимости интерфейса от других интерфейсов; 524.4.2, 21. Поддерживайте строгую типизацию интерфейсов; 524.4.2. 22. Выражайте интерфейсы на уровне типов приложения; 524.4.2. 23.
Выражайте интерфейс таким образом, чтобы запрос мог передаваться на удаленный сервер; 524.4.2. 24. Избегайте «жирных» интерфейсов; 524.4.3. 25. Применяйте закрытые поля данных и функции-члены где только можно; в24.4.2. 26. Применяйте альтернативу ргюгаге! ргогеегеИ для различения нужд разработчиков производных классов и обычных пользователей; 524.4.2. 27. Для обобшенного программирования применяйте шаблоны; 524.4 1. 28. Пользуйтесь шаблонами для параметризации политики выполнения алгоритмов; 524.4.1.
29. Применяйте шаблоны, если нужно, чтобы разрешение типов производилось на этапе компиляции; 524.4.1. 30. Используйте иерархии классов, когда разрешение типов необходимо на этапе выполнения; 524.4.1. Роли классов Кое-что можно и поменять, но, фундаментальные вопросы должны быть незыблемыми. — Стивен Дж. Гульд Разновидности классов — конкретные типы — абстрактные типы — узловые классы — изменение интерфейсов — объектный ввод/вывод — операции — интерфейсные классы — дескрипторные классы — прикладные среды разработки — советы — упражнения.
25.1. Разновидности классов Класс в языке С++ — это конструкция, которая отвечает разнообразным проектным целям. Я нахожу, что многие запутанные проблемы проектирования часто решаются введением нового класса, представляющего понятие, которое в предыдущем варианте проекта не было выявлено явным образом (при этом иногда приходится и удалять классы). Множество ролей классов в проекте приводит ко множеству видов классов в программе, специально приспособленных для решения конкретных задач. В настоящей главе рассматривается несколько классов-архетипов с присущими им достоинствами и недостатками: ° 825.2 Конкретные типы ° в25.3 Абстрактные типы ° 525.4 Узлы ° 825.5 Операции ° 825.6 Интерфейсы ° 825.7 Дескрипторные классы (папд!ез) ° 825.8 Прикладные среды разработки Эти разновидности классов являются проектными понятиями, а не языковыми конструкциями.
В идеале (наверное, недостижимом), хорошо бы иметь минималь- 896 Глава 25. Роли классов ный набор простых и ортогональных друг другу классов, из которых можно было составлять любые полезные и эффективно работающие классы. Отметим, что в проектах могут пригодиться любые из этих разновидностей классов, и ни одна из них по своей природе не лучше других в общем случае. Большая путаница при обсуждении вопросов проектирования и программирования исходит от людей, предпочитающих лишь одну или две разновидности классов. Делается это, якобы, ради простоты, но на деле приводит лишь к неуклюжему и неестественному применению любимых разновидностей классов. Мы здесь будем рассматривать чистые формы перечисленных разновидностей классов, хотя можно использовать и гибридные формы.
Гибрид, однако, должен появляться в результате осознанного проектного решения, а не в результате отказа от четкого выбора. Откладывание решения выбора на потом часто является завуалированной формой «отказа думать». Новичкам лучше держаться подальше от гибридов и придерживаться стилей уже существующих компонентов, имеющих характеристики, похожие на те, что требуется получить от нового решения. Только опытным программистам по плечу писать компоненты и библиотеки общего назначения, предназначенные для длительного использования, и которые нужно хорошо документировать и поддерживать все это время.
См. 923.5.1. 25.2. Конкретные классы (типы) Такие классы, как геегог (э!6.3), !Ы (917.2.2), Ваге (э!0.3) и сотр!ех (91!.3, в22.5), являются конкретными (солсгеге) в том смысле, что каждый из них представляет относительно простое понятие вместе с операциями, достаточными для поддержки этого понятия.
Кроме того, в каждом из этих классов имеется взаимнооднозначное соответствие между интерфейсом и реализацией„и ни один из этих классов не предназначен для создания производных классов. Как правйло, конкретные классы не вписываются в иерархии связанных классов. Каждый конкретный тип понятен сам по себе с минимальнейшей отсылкой к другим классам. Если конкретный тип хорошо реализован, то использующие его программы столь же малы по размеру и столь же эффективны, как и варианты программы, в которых программист самостоятельно вручную кодирует специализированные версии той же самой концепции.
Аналогично, если реализация изменяется существенно, изменяется и интерфейс для отражения этих изменений. Во всем этом конкретный класс напоминает встроенные типы (естественно, все встроенные типы конкретны). Определяемые пользователем конкретные типы, такие как комплексные числа, матрицы, сообщения об ошибках и т.д., часто представляют собой фундаментальные понятия для определенных предметных областей.
Точная природа классового интерфейса определяет, какие изменения в реализации имеют существенное значение в данном контексте; более абстрактные интерфейсы оставляют больше простора для изменения в реализации, но могут приводить к снижению быстродействия. Хорошая реализация не зависит от других классов больше, чем это необходимо, в результате чего снижаются затраты на «притирку» с иными классами как во время компиляции программы, так и во время ее выполнения.
25.2. Конкретные классы (типы) 897 1. Как можно точнее соответствовать частной концепции и стратегии ее реализации. 2. Обеспечивать затраты памяти и быстродействие, сравнимые с таковыми со специализированными «ручными» вариантами кода, за счет применения встраивания и операций, использующих все конкретные особенности концепции и ее реализации.
3. В минимальной степени зависеть от других классов. 4. Быть понятным и пригодным к использованию независимо от других классов. В результате достигается тесная связь между кодом реализации и кодом пользователя класса. Если реализация как-то меняется, пользовательский код придется перекомпилировать, поскольку пользовательский код почти всегда содержит обращение ко встраиваемым функциям илн внутренним переменным конкретного класса. Название «конкретный тип» было выбрано по контрасту с распространенным термином «абстрактный тип».
Взаимосвязь между конкретными и абстрактными типами обсуждается в 925.3. Конкретные типы непосредственным образом не выражают отношения общности. Например, !и! и гесгог предоставляют схожие наборы операций и могут взаимозаменяемо использоваться в шаблонах функций. Тем не менее, никакого отношения общности у классов !(и<!не> и гесгог<упг>, или между !Ьг<о2)аре*> и !!в!< Свес!е*> (9!3.6.3), нет, хотя мы сами-то и можем это сходство углядеть. Для наивно спроектированных конкретных типов это приводит к тому, что код, использующий их схожим образом, будет выглядеть по-разному. Например, итерации по списку АЫ при помощи функции пехг() совсем не похожи на итерации по типу )'ее!ос при помощи индексации: гоЫ ту (!.Ыга в!) ( Тог(Т* р = в!.у!ге!О г р; р = в!.пехг() ) ( ~Умой код У... д естественная итерация по списку гоИуоиг () ее!оса г) ( !ог(!пг ! = О; г<г.в!се (); !««) ( !! ваш код ) 2..
У естественная итерация по вектору Разница в стиле итераций вполне естественна в том смысле, что операция «получить следующий элемент» органична для списков, но не для векторов, а опера- Таким образом, подводя итог, можно сказать, что конкретный класс нацелен на то, чтобы: 898 Глава 25. Роли классов ция индексации является фундаментальной для векторов (но не для списков).
Наличие органичных для выбранной стратегии реализации контейнера операций критически важно с точки зрения достижения высокой эффективности и удобства записи. Неприятность такого подхода состоит в том, что программы для фундаментально схожих операций, используемых в двух приведенных выше циклах, выглядят различно, и код, применяющий конкретные типы для логически эквивалентных операций, не взаимозаменяем. В итоге, в программах реальных размеров выявить такие схожести бывает очень сложно, и сложно бывает перепроектировать систему для эксплуатации обнаруженной общности. Удачным примером перепроектирования, позволившим задействовать сходство между конкретными типами, не пожертвовав при этом ни элегантностью, ни эффективностью, являются стандартные контейнеры и алгоритмы (916.2). Если функция принимает аргумент конкретного типа, уже не будет никакой возможности принять менее специфичный аргумент (как это бывает в отношениях наследования).
Поэтому попытка задействовать сходство между конкретными типами с неизбежностью приводит к использованию шаблонов и обобщенного стиля программирования, как описано в 83.8. Для стандартной библиотеки итерации принимают следующий вид: гетрЫе<с(паг С> кеЫ оигз (Сь с) ( ~ог (брепате С::йепиос р=с.аея(п (); р! =с.епй(); еер) йитерации стандарт. биб-ки ( ~7... ) ) Здесь использовано фундаментальное сходство между контейнерами, открывающее дополнительные возможности, используемые стандартными алгоритмами (глава 18). Чтобы эффективно применять конкретные типы, пользователь должен хорошо понимать его частные детали.