- •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.6.3. Сохранение инварианта представления
Для того чтобы продемонстрировать корректность реализации типа, мы должны, в частности, показать, что инвариант представления сохраняется для всех законных объектов представления. Мы можем сделать это следующим образом. Прежде всего мы покажем, что инвариант сохраняется для объектов, возвращаемых операциями (как, например, операцией poly$create), которые возвращают объект типа, но не имеют аргументов типа. Для всех других операций мы можем предположить, что при их вызове инвариант сохраняется для всех аргументов, являющихся объектами типа; тогда мы должны показать, что он сохраняется при возврате как для аргументов типа, так и для возвращаемых объектов типа.
Например, реализация набора intset (рис. 4.5) имеет инвариант
% Для всех целых i, j, таких, что low (r) i < j bigh (r) % r [i]~=r lj]
Операция intset$create отвечает этому инварианту потому, что заново созданный массив пуст. Операция member также сохраняет этот инвариант, так как мы знаем, что инвариант сохраняется для аргумента s и что эта операция не модифицирует s. Операция insert также сохраняет инвариант, так как
1) инвариант сохраняется для ее аргумента s при обращении;
2) при вызове операции member из операции insert инвариант сохраняется потому, что он сохраняется в операции member; и
3) операция insert добавляет х к s, только если выражение ^member (s, х) имеет значение true; следовательно, так как набор s удовлетворяет инварианту при вызове, он также удовлетворяет инварианту после добавления к нему х.
В качестве второго примера рассмотрим реализацию полиномов poly, представленную на рис. 4.7. Вспомним, что инвариант есть
% low (r) Ss О
% & if empty (s) then high (r) == 0 % else г How (r) ] ~ = 0 & г [high (г) I ~ == 0
Операция poly$create сохраняет инвариант потому, что она создает нулевой полином. Операция mill сохраняет инвариант потому, что
1) при вызове инвариант сохраняется для р и q;
2) случай, когда р или q — нулевой полином, распознается и создается соответствующее представление; и
3) в противном случае ни р, ни q не содержат нулей ни в старшем, ни в младшем члене; следовательно, ни младший элемент возвращаемого массива (bottom (р)* bottom (q)), ни старший элемент (top (р)* top (q)) не может быть нулем.
Здесь мы предполагаем, что абстрактные объекты нельзя модифицировать вне их реализации. Если это возможно, то мы не можем предполагать, что инвариант сохраняется при вызове операции, так как какие-то модули могут его изменить.
4.5.4. Изменяемые представления
Изменяемость (или неизменяемость) — это свойство абстракции, которое должно сохраняться реализацией. Изменяемые абстракции должны иметь изменяемые представления, иначе будет невозможно обеспечить требуемую изменяемость. Однако неизменяемые абстракции не обязательно должны иметь неизменяемые представления. Например, полиномы неизменяемы, но имеют изменяемое представление. Изменяемые представления приемлемы, если модификации, сделанные в представлении, не могут быть замечены пользователями абстракций.
Иногда полезно создавать объект, изменяя его представление, однако, когда объект создан, его представление больше не модифицируется. Так, например, созданы полиномы. Изменяемость также полезна для благоприятных побочных эффектов, являющихся модификациями, которые не видны вне реализации. Например, давайте представим рациональное число парой целых:
гер =• record lnum, denom: int]
Функция абстракции есть
% типичное рациональное число есть n/d % Функция абстракции есть % А (г) = r.num/r.denom
Исходя из этого представления, мы должны принять несколько решений: что делать с нулевым знаменателем, как хранить отрицательные рациональные числа и хранить ли рациональные числа в сокращенной форме (т. е. когда числитель и знаменатель не имеют общего делителя). Мы решаем: исключить нулевой знаменатель, представлять отрицательные рациональные числа с помощью отрицательного числителя и не хранить представления в сокращенной форме (для ускорения таких операций, как умножение). Таким образом, имеем
% Инвариант представления есть % r.denom > О
Давайте также решим находить сокращенную форму при проверке двух рациональных чисел на равенство. Это мы можем сделать, используя процедуру gcd.
gcd = proc (n, d: int) returns (int) requires n и d должны быть положительными effects Возвращает макс. общий делитель n и d.
Реализация операции equal представлена на рис. 4.9. Однажды найденные, сокращенные формы записываются в представление, так как это ускоряет работу при последующих обращениях к операции equal.
rep == record [nurn, denom: int] equal == proc (rl, r2: cvt) returns (bool) if rl.nurn =: 0 then return (r2.niim = 0) elseif r2.niim == 0 then return (false) end reduce (rl) reduce (r2)
return (rl.num == r2.num cand rl.denom = r2.denom) end equal
% reduce — внутренняя программа, приводящая ее аргумент к сокращенной
% форме reduce proc (r: rep) g: int :== gcd (in1$abs (r.num), r.denom) r.num :== r.num/g r.denom := r.denom/g etid reduce Рис, 4.9. Благоприятный побочный эффект.
Модификация представления, осуществляемая операцией equal, — благоприятный побочный эффект. Такие побочные эффекты часто используются для улучшения эффективности. Они возможны, если функция абстракции отображает различные элементы в один, так как тогда имеется много объектов представления, которые представляют один и тот же абстрактный объект. Иногда полезно внутри реализации переходить от одного такого объекта представления к другому. Такой переход законен, так как новое представление соответствует тому же самому абстрактному объекту.
