Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
38
Добавлен:
23.03.2015
Размер:
884.22 Кб
Скачать

4.9.3. Полнота

Тип данных является полным, если он обеспечивает достаточно операций для того, чтобы все требующиеся пользователю работы с объектами могли быть проделаны с приемлемой эффективностью. Строгое определение полноты дать невозможно, хотя существуют пределы относительно того, насколько мало операций может иметь тип, оставаясь при этом приемлемым. Например, для наборов intset мы предоставили только операции create, insert и delete, и программы не могут получить никакой информации относительно элементов набора (так как не имеется наблюдателей). С другой стороны, если мы добавим к этим трем операциям только одну операцию size, то сможем иметь информацию об элементах набора (например, можем проверять на принадлежность, удаляя целые числа и анализируя, изменился ли размер), но тип в этом случае получится неэффективным и неприемлемым.

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

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

Чтобы решить, имеет ли абстракция данных достаточно опе­раций, установите прежде всего все, что пользователи могут пожелать делать с ней. Затем продумайте, как эти вещи могут быть осуществлены с использованием данного набора операций. Если что-то окажется слишком неэффективным или слишком громоздким (или и то и другое вместе), исследуйте, может ли этому помочь какая-нибудь дополнительная операция. Иногда суще­ственное улучшение в производительности можно получить, просто открыв доступ к представлению. Например, мы можем исключить операцию member для наборов intset, так как эта операция может быть реализована вне типа при помощи других операций. Однако проверка на принадлежность набору — опе­рация общего использования, и будет работать быстрее, если . выполняется внутри реализации. Следовательно, тип данных intset должен иметь эту операцию.

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

Абстракции данных

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

4.9,4. Аназиз типов данных

Чтобы показать, что программа, которая использует тип дан­ных, корректна, мы должны проанализировать свойства объектов типа. Этот анализ осуществляется на абстрактном уровне, т. е. мы используем спецификацию типа и игнорируем его реализацию. Такой анализ подробно обсуждается в гл'. II.

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

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

2. Показать, что свойство сохраняется для объектов, возвра­щаемых конструкторами, предполагая, что свойство сохраняется для объектов, передаваемых конструкторам в качестве аргументов.

3. Показать, что при выходе из модификатора свойство сохра­няется для модифицированных объектов, предполагая, что свой­ство сохраняется при вызове модификатора.

Например, интересное свойство упорядоченных списков (см. рис. 4.6) заключается в том, что они не содержат дублирующих друг друга элементов (что определяется операцией t$equal). Этот инвариант полезен для определения корректности реализации наборов intset с использованием упорядоченных списков. То, что это свойство является инвариантом, устанавливается следующим образом: 1) оно сохраняется для единственного примитивного конструктора create, так как операция create возвращает новый пустой список; 2) оцо сохраняется для операции addel (s, х). При возврате из этой операции в s содержатся те же элементы, которые там были до вызова, плюс элемент х. По предположению, до вызова в s нет дублирующих друг друга элементов. Кроме того, операция addel требует (в предложении requires), чтобы х не содержался в s. Следовательно, набор s с добавленным элемен­том х не содержит дублирующих друг друга элементов; 3) оно сохраняется для операции remel (s, х), так как оно сохраняется 4*

100 Глава 4

для s (по предположению), а операция remel только удаляет эле­менты из s.

Этот способ анализа называется индукцией типа данных. Индукция осуществляется по количеству вызовов процедур, исполь­зуемых для получения текущего значения объекта. Первый шаг индукции — это установить сохранение свойства для примитив­ных конструкторов. Индукционный шаг устанавливает сохран­ность свойства для конструкторов и модификаторов.

Отметим, что анализ осуществляется на абстрактном уровне, а не на уровне реализации. Нас не интересует, как реализованы упорядоченные списки. Вместо этого мы работаем прямо с их спецификацией. Работа на абстрактном уровне существенно упро­щает анализ.

Индукция также используется для того, чтобы показать, что реализации операций сохраняют инвариант представления (см. разд. 4.5.3). Однако этот анализ производится на уровне реали­зации. Как и в нашем случае, первый шаг — это установить сохранение свойства для примитивных конструкторов. Если пред­ставление изменяемое, на индукционном шаге необходимо рас­сматривать все операции, так как любая из них может изменить представление. Если же представление неизменяемое, то на ин­дукционном шаге необходимо рассмотреть только конструкторы.

Соседние файлы в папке POSIBNIK