Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
Некоторые ограничения могут навязываться реализацией. Например, в программе put из класса STACK2 присутствие в качестве предусловия require not full связано с реализацией стека на основе массива. Но это не является нарушением принципа, поскольку класс STACK2 в полном соответствии с его спецификацией определяет стеки ограниченной емкости, что отражено, например, в предложении indexing этого класса. АТД, служащий в роли спецификации этого класса, не задает наиболее общий вид стеков, но является понятием стека ограниченной емкости.
Обычно следует избегать структур ограниченной емкости; даже в случае массивов можно строить стек на динамических массивах, изменяющих размерность при необходимости. В Базовой библиотеке представлен общий класс, описывающий стеки11.1), отличающийся от класса STACK2 тем, что в нем не используется понятие емкости; стек по умолчанию перестраивается, когда текущей емкости недостаточно для хранения очередного поступающего элемента.Предусловия и статус экспорта
Возможно, вы заметили необходимость дополнительного требования, не отраженного в принципе обоснованности предусловия. Для того чтобы клиент мог проверить предусловие, оно не должно использовать закрытые свойства класса, недоступность которых отражена в статусе экспорта.
Рассмотрим следующую ситуацию:
-- Предупреждение: это неправильный класс, только в целях иллюстрации.
class SNEAKY feature
tricky is
require
accredited
do
...
end
feature {NONE}
accredited: BOOLEAN is do ... end
end
Спецификация для tricky устанавливает, что любой вызов этой процедуры должен удовлетворять условию, выраженному булевой функцией accredited. Но при экспорте класса эта функция для клиентов является закрытой, поэтому у них нет способа проверить выполнимость условия перед вызовом tricky. Очевидно, подобная ситуация неприемлема.
Причина, по которой принцип Обоснованности предусловия не покрывает подобные ситуации, в том, что это методологический принцип, а мы нуждаемся в правиле языка, заставляющем компилятор контролировать решение проблемы, не полагаясь на разработчиков.
Это правило учитывает все возможные ситуации экспорта, а не только случаи доступности всем клиентам (tricky) или полной недоступности (accredited). Как отмечалось, при обсуждении проблемы скрытия информации, компонент класса можно сделать доступным для некоторых клиентов, явно перечислив их в feature предложении, например feature {A, B, ... }, определяющего доступность только для классов A, B, ... и их потомков. Сформулируем правило языка:
Правило Доступности предусловия
Каждый компонент, появляющийся в предусловии программы, должен быть доступен каждому клиенту, которому доступна сама программа.
В соответствии с этим правилом каждый клиент, способный вызвать программу, способен проверить ее предусловие. По этому правилу класс SNEAKY является коварным, некорректно построенным, поскольку экспортирует tricky с недоступным предусловием. Нетрудно превратить этот класс в правильно построенный, изменив статус экспорта у accredited. Если tricky появится с предложением feature в форме feature {A, B, C}, то accredited должна экспортироваться, по меньшей мере, клиентам A, B, C, появляясь в той же группе feature, что и tricky. Можно задать для accredited собственное feature-предложение в одной из форм: feature {A, B, C}, feature {A, B, C, D, ...} или просто feature. Любое нарушение этого правила приведет к ошибке в период компиляции. Класс SNEAKY, например, будет забракован компилятором.
Подобного правила нет для постусловий. Не является ошибкой в постусловии ссылаться на компоненты, закрытые или экспортируемые избранным клиентам. Просто это означает, что описание эффекта выполнения программы содержит некоторые свойства, непосредственно не используемые клиентом. Подобная ситуация имеет место в процедуре put класса STACK2:
put (x: G) is
-- Добавить элемент x на вершину
require
not full
do
...
ensure
... Другие предложения...
in_top_array_entry: representation @ count = x
end
Последнее предложение в постусловии устанавливает, что элемент массива с индексом count содержит последний втолкнутый в стек элемент. Это свойство реализации, хотя put обычно доступно (экспортируется всем клиентам), массив representation является закрытым. Но ничего ошибочного в постусловии нет. Оно просто включает наряду со свойствами, полезными для клиентов ("Другие предложения"), свойство, имеющее смысл только для тех, кто знаком с полным текстом класса. Такие закрытые предложения не будут появляться в краткой форме класса - документации, предназначенной для авторов клиентских модулей.
Толерантные модули
(При первом чтении этот раздел можно опустить или ограничиться его беглым просмотром.)
Простые, но не защищенные модули могут быть не достаточно устойчивыми для использования их у произвольных клиентов. В таких случаях возникает необходимость создания нескольких классов, играющих роль фильтров. В отличие от ранее рассмотренных фильтров, устанавливаемых между внешним миром и обрабатывающими модулями, новые фильтры будут устанавливаться между "беспечными" клиентами с одной стороны и незащищенными классами с другой стороны.
Хотя было показано, что обычно это не лучший подход к проектированию, полезно рассмотреть, как выглядят классы, если использовать толерантный стиль в некоторых особых случаях. Класс STACK3, представленный ниже, иллюстрирует эту идею.
Поскольку классу понадобятся целочисленные коды ошибок, удобно для этой цели использовать ранее не введенную нотацию "unique" для целочисленных констант. Если объявить множество атрибутов следующим образом:
a, b, c, ...: INTEGER is unique
то в результате этого объявления a, b, c получат последовательно идущие целочисленные значения. Эти значения будут даваться компилятором с гарантией того, что все объявленные таким образом константы получат различные значения (будут уникальными). По принятому соглашению, всем объявляемым таким образом константам даются имена, начинающиеся с буквы в верхнем регистре и с остальными символами в нижнем регистре, например Underflow.
Вот написанная в этом стиле толерантная версия нашего класса стек. Заметьте, что этот текст, возможно пропущенный при первом чтении, включен только для понимания толерантного стиля. Он не является примером рекомендуемого стиля проектирования по причинам, обсуждаемым ниже, но которые достаточно ясны при просмотре этого текста.
indexing
description: "Стеки: Структуры с политикой доступа Last-In, First-Out %
%Первый пришел - Последний ушел, с фиксированной емкостью; %
%толерантная версия, устанавливающая код ошибки в случае %
%недопустимых операций."
class STACK3 [G] creation
make
feature - Initialization (Инициализация)
make (n: INTEGER) is
-- Создать стек, содержащий максимум n элементов, если n > 0;
-- в противном случае установить код ошибки равным Negative_size.
-- Без всяких предусловий!
do
if capacity >= 0 then
capacity := n
create representation.make (capacity)
else
error := Negative_size
end
ensure
error_code_if_impossible: (n < 0) = (error = Negative_size)
no_error_if_possible: (n >= 0) = (error = 0)
capacity_set_if_no_error: (error = 0) implies (capacity = n)
allocated_if_no_error: (error = 0) implies (representation /= Void)
end
feature - Access (Доступ)
item: G is
-- Элемент вершины, если существует; в противном случае
-- значение типа по умолчанию.
-- с ошибкой категории Underflow.
-- Без всяких предусловий!
do
if not empty then
check representation /= Void end
Result := representation.item
error := 0
else
error := Underflow
-- В этом случае результатом является значение по умолчанию
end
ensure
error_code_if_impossible: (old empty) = (error = Underflow)