Россия, г. Оренбург |
OO-программирование и язык Ada
Скрытие представления: частная история
Пакет STACKS в том виде, как он задан, не реализует принцип скрытия информации. Объявления типов STACK и STACK_CONTENTS, находясь в интерфейсе, позволяют клиентам непосредственный доступ к представлению стеков. Например, клиент может включить код вида:
[1] use REAL_STACKS_1;... s: STACK; ... s.implementation (3) := 7.0; s.last := 51;
грубо нарушая основную спецификацию абстрактных типов данных.
Концептуально объявления типа должны находиться в теле. Почему их туда не помещают с самого начала? Объяснение находится вне языка и требует рассмотрения проблем программного окружения.
Одно из уже упомянутых требований к языку Ada состояло в возможности независимой компиляции пакета при наличии доступа к его интерфейсу, но необязательно к его телу. Принятая технология предполагала построение сверху вниз: для продолжения работы над модулем достаточно знать спецификацию необходимых ему средств. Действительная реализация могла появиться значительно позже.
Если есть доступ к интерфейсу REAL_STACKS_1 (то есть к интерфейсу STACKS, REAL_STACKS_1 является просто его родовым порождением), можно компилировать любого из его клиентов. Такой клиент будет содержать объявления вида:
use REAL_STACKS_1;... s1, s2: STACK; ... s2 := s1;
Компилятор не сможет их хорошо обрабатывать, не зная размера объекта типа STACK. Но это может определяться только из объявлений типа для STACK и вспомогательного типа STACK_CONTENTS.
Отсюда концептуальная дилемма, стоявшая перед проектировщиками языка Ada: вопросы реализации требуют помещения объявлений типа в рай - интерфейс, в то время как им место в аду - теле пакета.
Пришлось создать чистилище: специальный раздел пакета, физически видимый в интерфейсе и компилируемый с ним, но такой, что клиенты не могут обращаться к его элементам. Чистилище - это закрытая часть интерфейса, она вводится ключевым словом private. Любое объявление, появляющееся здесь, недоступно клиентам. Эта схема иллюстрируется нашей последней версией интерфейса пакета, задающего стек:
generic type G is private; package STACKS is type STACK (capacity: POSITIVE) is private; procedure put (x: in G; s: in out STACK); procedure remove (s: in out STACK); function item (s: STACK) return G; function empty (s: STACK) return BOOLEAN; Overflow, Underflow: EXCEPTION; private type STACK_VALUES is array (POSITIVE range <>) of G; type STACK (capacity: POSITIVE) is record implementation: STACK_VALUES (1..capacity); count: NATURAL := 0; end record end STACKS;
Отметим, тип STACK теперь должен объявляться дважды: сначала в открытой части интерфейса, где он специфицируется как private, затем еще раз в закрытой части, где дается полное описание. Без первого объявления строка вида s: REAL_STACK не будет разрешенной в клиенте, поскольку доступ есть только к сущностям, объявляемым в открытой части. Первое объявление, специфицируя тип как private, запрещает клиентам доступ к любым свойствам помимо универсальных операций: присваивания, проверки на равенство и использование в качестве фактических аргументов.
Заметьте, тип STACK_VALUES чисто внутренний и не нужен клиентам. Поэтому он не объявляется в открытой части интерфейса пакета.
Важно понять, что информация, помещаемая в закрытую часть интерфейса, должна была быть в теле пакета и появляется в спецификации пакета только по причинам реализации языка. С новой формой STACKS клиентский код, выше помеченный как [1], имевший прямой доступ к представлению в клиенте, становится неправильным.
Авторы клиентских модулей могут видеть внутреннюю структуру экземпляров STACK, но они не могут воспользоваться ею в своих модулях. Это могло бы приводить разработчиков к танталовым мукам. (Хорошая среда языка Ada могла бы скрывать эту часть от клиента, также как это делает инструмент short, описанный в предыдущих лекциях.) Удивительная для новичков, эта политика не противоречит правилу скрытия информации. Как отмечалось ранее, цель скрытия не в том, чтобы не дать авторам клиента возможности прочитать скрытые подробности, а чтобы не дать им использовать эти подробности.
Те, кому хотелось бы все усложнить, могли бы подвести итог в двух предложениях (произнесенных очень быстро, чтобы произвести впечатление и на друзей, и на врагов):. Закрытый раздел интерфейса пакета перечисляет реализацию тех концептуально закрытых типов, которые должны быть объявлены в интерфейсе, хотя их реализация недоступна для использования. В открытой части интерфейса эти типы объявлены закрытыми.