Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 191
Текст из файла (страница 191)
Предусповия и постусповия Одним из распространенных применений утверждений является выражение предусловий и постусловий для функции. То есть проверка того, что выполняются основные предположения при входе в функцию (например, о входных данных), и что функция оставляет мир в правильном состоянии на выходе. К сожалению, утверждения, которые нам хотелось бы проверить, часто оказываются на более высоком уровне, чем язык программирования позволяет пам выразить удобным и эффективным образом.
Например: 1етр(а1е<с(акк Вап> оогд косе (1(апЕьгк1, Кап 1ак1) Глава 24. Проектирование и программирование 830 О сортирующпй алгоритм Аккегг<ра[1ес! кигг> // неправильная сортировка 2 )')[!гя1, !ая1) располагается в порядке возрастания'); // псевдокод Это фундаментальная проблема. То, что мы хотим сказать о программе, лучше всего выражается на языке высокого уровня, основанном на математике, а не на алгоритмическом языке программирования, на котором мы пишем программу. Что касается инвариантов, то для того, чтобы перевести идеальное утверждение, которое нам хотелось бы высказать, в нечто такое, что можно алгоритмически проверить, нужна определенная доля интеллекта.
Например: 1етр!а ге<с!азк Кап> иоЫ заг1 )Нап/сгк 1, Кап !аз!) // [/1<з1, !ам) — допустииая последовительностгс О проверяел~ правдопододие Аккегг<Вад ког1 зеуиепсе> ![ь[ВЕВ!!Б ))[сгк1>!аз!1 // сортирующий алгиритлс // [/т1, !аз1) располвгаепгся в порядке возрастания: // приверяея образел Акзег1<са!!ед ког1>)[ьГ>ЕВсйл)) )!ак1-/сгк1)<2)) Цггя1 =!акй-1) Ы *асят<=дг з1) )!аз1-Яя1)/2) 88 Вгкг) )!аят — /)гк1)/2]<= !аз1)-!)))); Часто я обнаруживаю, что проще писать обычный код с проверкой аргументов и результатов, чем составлять утверждения.
Однако важно попытаться выразить реальные (идеальные) предусловия и постусловия — н хотя бы документировать их в комментариях, — прежде чем свести их к чему-то менее абстрактному, что можно выразить на языке программирования, Проверка предусловий может легко выродиться в простую проверку значений аргументов. Поскольку один аргумент часто передается нескольким функциям, зта проверка может многократно повторяться и стать довольно дорогой. Однако простое утверждение, что все аргументы-указатели во всех функциях не равны нулю, не очень помогает и может вызвать ложное чувство безопасности — особенно если тесты выполняются только во время отладки, дабы предотвратить излишние затраты. Это главная причина того, почему я советую сосредоточить внимание на инвариантах.
24.3.7.4. Инкапсуляция Отметим, что в С++ единицей инкапсуляции является класс — а не отдельный обьект. Например: с!аяз Егз1 ( Е!я1" пех1; риЬ!!с; Ьва1 оп ))лз1'); ): Ьао!Е!яв:оп (с!зг р) ( ВЗ! 24.4. Компоненты К (р == О) ге!игл !а!ее; )ог (! !е1" 9 = ЯЬ; О; О=ч — лех!) !Т (р == О) гейигп 1гие; ге!игл)а!ее; Доступ к закрытому указателю 2.!з1спех! разрешен, поскольку функция Й!и1:оп () имеет доступ ко всем объектам класса А!з1, на которые она может как-то ссылаться, Там, где это неудобно, можно упростить положение, не пользуясь преимуществом доступа к представлению других объектов из функции-члена. Например: Ьоо1й!еноп (!хЫ.
*р) ( Ц' (р == О) ге!игл та!ее; (!' (р == 1(ие) ге1игп 1гие; (! (аех1 == О) ге!игл!а!ее; ге1игп пех1- оп (о); Однако зто превращает итерацию в рекурсию, что может нанести серьезный удар по быстродействию, если компилятор не способен оптимизировать и превратить рекурсию обратно в итерацию. 24.4. Компоненты Единица проектирования — это множество классов, функций и т, д., а не отдельный класс. Такое множество, часто называемое библиотекой или средойразработки Я 258), является также единицей повторного использования Я 23.55 ), сопровождения и т.
д. Для выражения понятия о наборе возможностей, объединенных неким логическим критерием, С+«- предоставляет три механизма: (1] классы, содержащие наборы данных, функций, шаблонов и типизированных членов; (2] иерархии классов, содержащие набор классов; [3] пространства имен, содержащее наборы данных, функций, шаблонов и типизированных членов. Класс обеспечивает множество средств, делающих более удобным создание объектов того типа, который он определяет. Однако механизье создания объектов единственного типа не лучшим образом описывает многие важные компоненты. Иерархия классов выражает понятие множества родственных типов.
Однако отдельные члены компоненты не всегда лучше всего выражаются классамн, и не все классы обладают достаточнымм сходством, требующимся, чтобы вписать их в осмысленную иерархию классов Я 24.2.5). поэтому самым прямым н универсальным воплощением идеи «компоненпюсти> является пространство имен. Компоненту часто называют «категорией классов». Однако не все элементы компоненты являются или должны являться классами.
В идеале компонента описывается набором интерфейсов, которыми она сама пользуетсяя для своей реализации, плюс набор интерфейсов, которые она предоставляет п ользователям. Все прочее — это «детали реализации», скрытые от остальной системы. Такой набор в самом деле может служить для проектировщика описанием компоненты. Чтобы реализовать ее, программисту нужно отобразить эти интерфейсы в объявления.
Классы и иерархии классов предоставляют интерфейсы, а пространства имен позволяют программисту сгруппировать интерфейсы и отделить используемые интерфейсы от предоставляемых интерфейсов. 832 Используемоереализацией Х Используемое интерфейсом Х Интер Ири использовании техники, описанной в 8 8.2А.1, это превращается в следующее: патеярасе А ( // нвкоторыв средства, используел~ые интерфейсон Х //- // интерфейс коипоненты Х // зависит от объявлений из А патеярасеХ ипр!( из!ау патеярасе Х; //средства, нужныедля реализацииХ //зависит от объявлений! из Х 1тр1 Универсальный интерфейс Хне должен зависеть от интерфейса реализации, то есть отХ 1тр!. Компонента может иметь много классов, не п реди азцаченн ых для общего использования.
Такие классы следует «прятать» внутрь классов реализации или пространств имен: патезрасеХ 1тр1( //детали реализации колпоненгпыХ с1аяя !РЪ(де! ( Это гарантирует, что (РЫде1не используется другими частями программы. Однако, классы, представляющие согласованные понятия, часто являются кандидатами на повторное использование, и поэтому следует рассмотреть включение их в интерфейс компоненты. и атеярасе Х ( ия1пу патезрасе А //- ооиЩ; ) зо!дХ Я ( иягпупатеярасеХ 1тр1; с1аяя Саг( с(аяя )Изее!( //-.
)' Чаев!/!гв,/гзв, г!гв, п.ав; //- Глава 24. Проектирование н программирование 833 24.4. Компоненты риИ!с В большинстве применений нам нужно спрятать действительные колеса (»гйее1з), чтобы сохранить абстракцию автомобиля !когда вы пользуетесь автомобилем, вы не можете независимо оперировать колесами).
Олнако класс колеса !е'Ьее! сам по себе кажется хорошей кандидатурой для более широкого использования, поэтому, может быть, его лучше вынести из класса Саг (автомобиль): с1аее !»"аее! ( 0- с1аее Саг( !»Ъее!Я~о,уста, г!ш, гсис риЫ!с: 0- Решение, вставлять один класс в другой или нет, зависит от целей проектирования и универсальности участвующих в процессе понятий. И вставка, и «не-вставка» класса являются широко применяемыми проектными приемами. По умолчанию класс следует максимально локализовать, пока не появится необходимость сделать его более доступным.
У «интересных» функций есть отвратительная тенденция «всплывать» в глобальное пространство имен, в широко используемые пространства имен или в высший базовый класс в иерархии. Это может запросто привести к непреднамеренному раскрытию деталей реализации и к проблемам, связанным с глобальными данными н функциями. С большой вероятностью это случается в иерархиях с одним корнем и в программах, где используется очень мало пространств имен. В контексте иерархии классов для борьбы с атнм явлением можно воспользоваться виртуальными базовыми классами Я 15.2А). Для избежания проблем в контексте пространств имен главным средством являются маленькие «реалнзационные» пространства имен. Отметим, что заголовочные файлы предоставляют мощный механизм обеспече ння разного представления компоненты для разных пользователей, а также для исключения классов, которые с точки зрения пользователя являются частью реализации Я 9.3.2).
24.4.1. Шаблоны С точки зрения проектирования шаблоны служат двум, мало связанным между собой целям; обобщенному программированию; политике параметризацни На ранних стадиях проектирования операции являются просто операциями. Позже, когда настает время описать типы операндов, для статически типизированных языков программирования, каковым является С++, шаблоны приобретают огромную 834 Глава 24. Проектирование и программирование важность, Без шаблонов определения функций стали бы повторяться, или проверка типов неизбежно отражалась бы на времени выполнения программы (5 24.2.3!. Операция, реализующая алгоритм для операндов разных типов, является кандидатоьчна реализацию в виде шаблона.
Ешли все операнды вписываются в единую иерархию классов, и особенно если есть необходимость добавлять новые типы операндов во время выполнения программы, тип операнда лучше всЕго представить классом — часто абстрактным классом. Если типы операнда не вписываются в единую иерархию, и особенно если критично быстродействие, эти операции лучше реализовать в виде шаблона. Стандартные контейнеры н поддерживающие их алгоритмы являются примером того, когда необходимость принимать операнды множества не связанных между собой типов в сочетании с требованиями быстродействия приводят к использованию шаблонов Я (б.2).