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

AlgStr / Библиотека / ЛЕКЦИИ / PZ00 / Тищенко Андрей

.doc
Скачиваний:
33
Добавлен:
23.03.2015
Размер:
50.69 Кб
Скачать

Вопрос № 16 :

Реализация абстракции данных. Функция инвариант представления. Сохранения инварианта представления.

Реализация абстракций данных

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

Например, возможное представление для объекта intset — это массив целых чисел, где каждое целое число набора intset соответствует элементу массива. Мы должны решить — должен ли каждый элемент набора встречаться в массиве только один раз или же он может встречаться много раз. В последнем случае опе­рация insert будет работать быстрее, однако операции delete и member будут выполняться медленнее. Если операция mem­ber используется часто, мы должны остановиться на первом слу­чае.

Заметим, что здесь мы говорим о двух разных типах: новом абстрактном типе intset, который мы реализуем, и массиве целых, который используется как представление. Каждая реализация будет иметь два таких типа: абстрактный тип и тип представ­ления (rep type). Предполагается, что с типом представления мы имеем дело только при реализации. Все, что мы можем делать с объектами абстрактного типа, — это применять к ним соответ­ствующие данному типу операции. Соблюдение этого ограниче­ния в языке CLU осуществляется с помощью контроля типов. Язык CLU удобен, поскольку в нем тип может быть реализован как отдельный программный модуль. Эта программная единица языка CLU называется кластер. Отсюда и название (CLU от слова CLUster.)

Внутри кластера должны быть доступны как абстрактный тип, так и тип представления. Более того, должна иметься воз­можность перехода от одного типа к другому. Например, опера­ция insert получает в качестве аргумента набор целых чисел, но для реализации insert необходимо использовать массив, который представляет этот набор.

Возможность перехода от абстрактного типа к типу представ­ления и обратно в языке CLU обеспечивают две специальные операции: up и down. Операция up использует в качестве аргу­мента объект представления и в качестве результата выдает абстрактный объект, а операция down осуществляет обратное действие. Для того чтобы обеспечить преобразование абстрактного типа в тип представления и обратно, каждый кластер имеет свою собственную версию операций up и down. Эти операции автоматически определяются компилятором CLU. Например, в кластере процедуры intset компилятор порождает операции up и down со следующими заголовками:

up = proc (a: array [int]) returns (intset) down = proc (s: intset) returns (array [int])

Операции up и down могут использоваться только в кластерах и всегда осуществляют переход от абстрактного типа к типу пред­ставления и обратно только для кластера, в котором они появи­лись. Поэтому операции up и down не могут отрицательно по­влиять на контроль типов языка CLU.

Функция инвариант представления

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

[1: 1, 7,6], [1: 1, 6, 6] и [1:6, 1,7]. Однако в реализации intset мы ре­шили, что каждый элемент набора записывается в массив только один раз.

Следовательно, законные представления наборов int­set в этом кластере не содержат дублирующих друг друга элемен­тов; [1: 1, 6, 6], например — незаконное представление.

Условие, которому удовлетворяют все законные объекты, называется инвариантом представления. Инвариант представле­ния 1 есть предикат1: ( rep bool. ) который принимает значение true для законных объектов пред­ставления. Например, для кластера intset мы можем дать следую­щий инвариант представления:

Функция ИНВАРИАНТ ПРЕДСТАВЛЕНИЯ есть

( Для всех целых i и j таких, что

low (r)<= i < j <= high (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 [int], size: int

Идея заключается в том, что для целых от 1 до 100 мы отме­чаем их принадлежность набору, записывая значение true в r.els [i] . Не принадлежащие этому диапазону целые записываются в other.els точно так же, как в нашей предыдущей реализации intset. Так как невыгодно вычислять размер набора intset, про­сматривая все элементы массива els, мы записываем этот размер в представление в явном виде. Это представление хорошо, если почти все члены набора принадлежат диапазону 1—100. (В про­тивном случае пространство, требующееся для массива els, будет использоваться неэффективно.) Таким образом, мы имеем

Функция абстракции есть : А (r) = {i-.other-els [i ] I low (r. other- els) i s$high (r.other, els)}

Инвариант представления есть 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 определена здесь рекурсивно.

Инвариант представления должен быть явно задан также и в этом случае. Он освобождает осуществляющего реализацию от возможной зависимости от какого-то более сильного инварианта.

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

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

Инвариант представления должен отражать все требования, от которых зависят операции, но может не отражать дополнительные требования. Хорошо представлять себе, что операции реализуют различные люди, которые не общаются друг с другом. Тогда понятно, что инвариант представления дол­жен содержать все требования, которые необходимо знать этим различным людям.

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

Все операции должны реализовываться с учетом инварианта представления. Например, предположим, что мы реализуем опе­рацию insert так:

insert == proc (s: cvt, x: inl) rep$addh (s, x) end insert

Эта реализация может создать объект с дублирующимися элементами. Если мы знаем, что инвариант представления за­прещает такие объекты, то эта реализация очевидно некорректна. Инвариант представления также полезен для того, кто изу­чает реализацию. Например, в альтернативной реализации набора intset мы можем решить подвергать массив представления сортировке. В этом случае мы будем иметь:

Инвариант представления есть: для всех i, j, таких, что low (r) < i < j < high (r): r [i] < r [j]

Все операции тогда будут реализовываться иначе. Инвариант представления говорит тому, кто изучает реализацию, почему операции реализованы именно так, как они реализованы.

Сохранение инварианта представления

Для того чтобы продемонстрировать корректность реализа­ции типа, мы должны, в частности, показать, что инвариант пред­ставления сохраняется для всех законных объектов представ­ления. Мы можем сделать это следующим образом. Прежде всего мы покажем, что инвариант сохраняется для объектов, возвра­щаемых операциями, которые возвращают объект типа, но не имеют аргументов типа. Для всех других операций мы можем предположить, что при их вызове инвариант сохраняется для всех аргументов, являющихся объектами типа; тогда мы должны показать, что он сохраняется при возврате как для аргументов типа, так и для возвращаемых объектов типа.

Например, реализация набора intset имеет инва­риант

для всех целых i, j, таких, что low (r) < i < j <high (r) : r [i] <> r [j]

Операция intset$create отвечает этому инварианту потому, что заново созданный массив пуст. Операция member также сохраняет этот инвариант, так как мы знаем, что инвариант сох­раняется для аргумента s и что эта операция не модифицирует s. Операция insert также сохраняет инвариант, так как

1) инвариант сохраняется для ее аргумента s при обращении;

2) при вызове операции member из операции insert инвариант сохраняется потому, что он сохраняется в операции member;

3) операция insert добавляет х к s, только если выражение member (s, х) имеет значение true; следовательно, так как на­бор s удовлетворяет инварианту при вызове, он также удовлетво­ряет инварианту после добавления к нему х.

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