- •1.Введение
- •1.1.Декомпозиция и абстракция
- •1.2.Абстракция
- •1.2.1.Абстракция через параметризацию
- •1.2.2.Абстракция через спецификацию
- •1.2.3.Виды абстракций
- •3.Процедурная абстракция
- •3.2.Спецификации
- •3.3.Спецификации процедурных абстракций
- •3.4.Реализация процедур
- •3.5.Более обобщенные процедуры
- •3.6.Создание процедурных абстракций
- •3.7.Заключение
- •4.Абстракции данных
- •4.1.Спецификации для абстракций данных
- •4.2.1.Реализация на языке clu
- •4.2.2.Замечания по поводу операций up и down
- •4.3.Использование абстракций данных
- •4.4.Реализация полиномов
- •4.5.Пояснения для понимания реализаций
- •4.5.1.Функция абстракции
- •4.5.2.Инвариант представления
- •4.6.3.Сохранение инварианта представления
- •4.5.4.Изменяемые представления
- •4.6.Параметризованные абстракции данных
- •4.7.Списки
- •4.8.Упорядоченные списки
4.5.2.Инвариант представления
В ориентированных на работу с типами языках (как, например, в языке CLLJ)контроль типов гарантирует, что если абстрактный объект типа передается как аргумент операции типа, то этот абстрактный объект представляется объектом типа представления. Во многих случаях, однако, не все объекты представления являются законными представлениями абстрактных объектов. Например, для кластера intset(рис. 4.5)каждый массив независимо от значений является элементомЯ',например, массивы [1: 1, 7,6], [1: 1, 6, 6]и [1:6, 1,7].Однако в реализации intsetмы решили, что каждый элемент набора записывается в массив только один раз. Следовательно, законные представления наборов intsetв этом кластере не содержат дублирующих друг друга элементов; [1: 1, 6, 6],например —незаконное представление.
Условие, которому удовлетворяют все законные объекты, называется инвариантом представления.Инвариант представления 1есть предикат
1: rep
bool.
Абстракции данных
i < j ^ high (r)
который принимает значение trueдля законных объектов представления. Например, для кластера intsetмы можем дать следующий инвариант представления:
%Инвариант представления есть%Для всех целых i, j,таких, что low (r): о/о r [i]~=r [j]
Заметим, что для массива II: 1, 6, 6] 1принимает значениеfalse,а для массивов [1: 1, 7, 6]и [1: 6, 1, 7]—значение true. Заметим также, что мы не включаем в 1условия, которые принимают значение trueдля всех массивов (например, size (r) == = high (r) — low (r) + 1),потому что это гарантируется процедурами работы с массивами и соответственно предполагается в реализации intset.
В качестве второго примера инварианта представления рассмотрим альтернативное представление наборов intset,состоящее из булева массива размерности 100и массива целых:
гер = record leis: array [bool], other.els: array tint], size: int]
Идея заключается в том, что для целых от 1до 100мы отмечаем их принадлежность набору, записывая значение trueвr.els ti 1.Не принадлежащие этому диапазону целые записываются в other.elsточно так же, как в нашей предыдущей реализацииintset.Так как невыгодно вычислять размер набора intset,просматривая все элементы массива els,мы записываем этот размер в представление в явном виде. Это представление хорошо, если почти все члены набора принадлежат диапазону 1—100.(В противном случае пространство, требующееся для массива els,будет использоваться неэффективно.) Таким образом, мы имеем
%Функция абстракции есть о/о А (r) = {i-.other-els [i ] I low (r. other- els) ^ i s$ high (r.other, els)}
% U % {j U^js5 100 & r'els [j])
Здесь Uобозначает математическое объединение двух наборов. Итак,
%Инвариант представления есть% size (r.els) = 100 & low (r.els) = I % &все элементы r.otherне принадлежат диапазону I—100 % &в r.otherнет дубликатов элементов% & r.size = size (r.other, els) -(-%(число элем. со знач. trueв r.els)
Заметим, что поле sizeв этом представлении излишне: оно содержит информацию, которая может быть получена из остальной части представления путем вычислений. Но, поскольку такая излишняя информация все же содержится в представлении, взаимосвязь этой информации с остальной частью представления должна быть объяснена в инварианте представления (например, последняя строка нашего инварианта представления).
Иногда удобно использовать в инварианте представления или в функции абстракции вспомогательную функцию. Например, последняя строка инварианта представления может быть переписана следующим образом:
% r.size= size (r.othei_els)+ cnt (r.els, low(т.els)) %где cnt (a, i) = if i »> high (a) then 0 % else if r [i] then I+cnt (a, i+1) % else cnt (a, i + 1)
Вспомогательная функция cntопределена здесь рекурсивно. Интересный инвариант представления имеет реализация полиномов poly,представленная на рис. 4.7.Вспомним, что мы решили не записывать нулевые коэффициенты справа и слева от значащих членов полинома и представлять нулевой полином пустым массивом. Следовательно, мы предполагаем, что старший и младший элементы массива —не нули. Кроме того, эти массивы всегда имеют неотрицательную нижнюю границу. И наконец, пустой массив (представляющий пустой полином) должен иметь верхнюю границу, равную нулю. Таким образом, мы имеем
%Инвариант представления есть% low (r) ^.0
% & if empty(т) then high (r) = 0 % else r (low (r) ]~=0 &r [high (r)] ~=0
Вспомним, что реализация операции degreeпредполагает, что верхняя граница массива равна степени полинома. Теперь мы видим это требование в инварианте представления.
Иногда все объекты представления законны. Тогда мы просто имеем
%Инвариант представления есть% true
Инвариант представления должен быть явно задан также и в этом случае. Он освобождает осуществляющего реализацию от возможной зависимости от какого-то более сильного инварианта.
Инвариант представления —это «инвариант» потому, что он всегда сохраняется для представлений абстрактных объектов, т. е. он сохраняется, если объект используется вне его реализации. Инвариант представления не должен, однако, обязательно сохраняться всегда: он может быть нарушен при выполнении одной из операций типа. Например, операция poly$addможет получить массив с нулевым старшим и младшим элементом, но такие элементы удаляются из массива перед возвратом из операции add. Инвариант представления должен сохраняться при возврате из операций.
Между функцией абстракции и инвариантом представления имеется определенная взаимосвязь. Функция абстракции имеет смысл только для законных представлений, так как только они представляют абстрактные объекты. Следовательно, нет необходимости определять ее для незаконных представлений. Например, как мы упоминали раньше, функция абстракции для полиномов имеет некую область определения —она определена только в случае, если массив имеет неотрицательную нижнюю границу. Этому требованию удовлетворяют все законные представления полиномов, что отражено в инварианте представления.
Существует соглашениеотом, что должно быть отражено в инварианте представления. Инвариант представления должен отражать все требования, от которых зависят операции, но может не отражать дополнительные требования. Например, все конкретные объекты для реализации, представленной на рис. 4.5, имеют нижнюю границу, равную, 1,но от этого не зависит реализация никаких операций, поэтому нет необходимости отражать это в инварианте представления. Хорошо представлять себе, что операции реализуют различные люди, которые не общаются друг с другом. Тогда понятно, что инвариант представления должен содержать все требования, которые необходимо знать этим различным людям.
При реализации абстракции данных инвариант представления —одна из первых вещей, о которых должен подумать программист. Он должен быть выбран до реализации операций, в противном случае различные реализации не будут согласованы друг с другом. Чтобы гарантировать согласованность, инвариант представления должен быть написан и включен в программу как комментарий. Написание инварианта представления вынуждает того, кто занимается реализацией, ясно выразить то, что он хочет, и увеличивает тем самым вероятность того, что операции будут реализованы корректно.
Все операции должны реализовываться с учетом инварианта представления. Например, предположим, что мы реализуем операцию insertтак:
insert == proc (s: cvt, x: inl) rep$addh (s, x) end insert
Эта реализация может создать объект с дублирующимися элементами. Если мы знаем, что инвариант представления запрещает такие объекты, то эта реализация очевидно некорректна. Инвариант представления также полезен для того, кто изучает реализацию. Например, в альтернативной реализации набора intsetмы можем решить подвергать массив представления сортировке. В этом случае мы будем иметь
%Инвариант представления есть%Для всех i, j,таких, что low(г) sg; i < j ^ high (r) % r [i] <r [j]
Все операции тогда будут реализовываться иначе, чем на рис. 4.5.Инвариант представления говорит тому, кто изучает реализацию, почему операции реализованы именно так, как они реализованы.