В отличие от атрибутов-переменных, константы не занимают в памяти места. Их введение не связано с издержками в период выполнения, а потому не страшно, если их в классе достаточно много.
Использование констант
Вот пример, показывающий, как клиент может применять константы, определенные в классе:
class FILE feature
error_code: INTEGER; -- Атрибут-переменная
Ok: INTEGER is 0
Open_error: INTEGER is 1
...
open (file_name: STRING) is
-- Открыть файл с именем file_name
-- и связать его с текущим файловым объектом
do
error_code := Ok
...
if "Что-то не так" then
error_code := Open_error
end
end
... Прочие компоненты ...
end
Клиент может вызвать метод open и проверить успешность операции:
f: FILE; ...
f.open
if f.error_code = f.Open_error then
"Принять меры"
else
...
end
Нередко нужны и наборы констант, не связанных с конкретным объектом. Их, как и раньше, можно объединить в класс, выступающий в роли родителя всех классов, которым необходимы константы. В этом случае можно не создавать экземпляр класса:
class EDITOR_CONSTANTS
feature
Insert: CHARACTER is 'i'
Delete: CHARACTER is 'd'; -- и т.д.
...
end
class SOME_CLASS_FOR_THE_EDITOR
inherit
EDITOR_CONSTANTS
...Другие возможные родители ...
feature ...
... подпрограммы класса имеют доступ к константам, описанным в EDITOR_CONSTANTS ...
end
Класс, подобный EDITOR_CONSTANTS, служит лишь для размещения в нем группы констант, и его роль как "реализации АТД" (а это - наше рабочее определение класса) не столь очевидна, как в предыдущих примерах. Теоретическое обоснование введения таких классов мы обсудим позднее. Представленная схема работоспособна только при множественном наследовании, поскольку классу SOME_CLASS_FOR_THE_EDITOR могут потребоваться и другие родители.
Константы пользовательских классов
Символические константы полезны не только при работе с предопределенными типами, такими как INTEGER. Они нужны и тогда, когда их значениями являются объекты классов, созданных разработчиком. В этом случае решение не столь очевидно.
Константы с манифестом для этого непригодны
Первым примером служит класс, описывающий комплексное число:
class COMPLEX creation
make_cartesian, make_polar
feature
x, y: REAL
-- Действительная и мнимая часть
make_cartesian (a, b: REAL) is
-- Установить действительную часть a, мнимую - b.
do
x := a; y := b
end
... Прочие методы (помимо x и y, других атрибутов нет) ...
end
Пусть мы хотим определить константу - комплексное число i, действительная часть которого равна 0, а мнимая 1. Первое, что приходит в голову, - это буквальная константа вида
i: COMPLEX is "Выражение, определяющее комплексное число (0, 1)"
Как записать выражение после is? Для пользовательских типов данных никакой формы записи неименованных констант не существует.
Можно представить себе вариант нотации на основе атрибутов класса:
i: COMPLEX is COMPLEX (0, 1)
Но этот подход, хотя и реализован в некоторых ОО-языках, противоречит принципу модульности - основе объектной методологии. Приняв этот подход, мы согласились бы с тем, что клиенты COMPLEX должны описывать константы в терминах реализации класса, а это нарушает принцип Скрытия информации.
Кроме того, как гарантировать соответствие неименованной константы инварианту класса, если таковой имеется?
Последнее замечание позволяет найти правильное решение. Мы уже говорили о том, что в момент рождения объекта ответственность за соблюдение инварианта возлагается на процедуру создания. Создание объекта иным путем (помимо безопасного клонирования clone) ведет к ситуациям ошибки. Поэтому мы должны найти путь, основанный на обычном методе создания объектов класса.
Однократные функции
Пусть константный объект - это функция. Например, i можно (в иллюстративных целях) описать внутри самого класса COMPLEX как
i: COMPLEX is
-- Комплексное число, re= 0, а im= 1
do
create Result.make_cartesian (0, 1)
end
Это почти решает нашу задачу, поскольку функция всегда возвратит ссылку на объект нужного вида. Коль скоро мы полагаемся на обычную процедуру создания объекта, условие инварианта будет соблюдено, - как следствие, получим корректный объект.
Однако результат не соответствует потребностям: каждое обращение клиента к i порождает новый объект, идентичный всем остальным, а это - трата времени и пространства. Поэтому необходим особый вид функции, выполняемой только при первом вызове. Назовем такую функцию однократной (once function). В целом она синтаксически аналогична обычной функции и отличается лишь служебным словом once, начинающего вместо do ее тело:
i: COMPLEX is
-- Комплексное число, re= 0, im= 1
once
create Result.make_cartesian (0, 1)
end
При первом вызове однократной функции она создает объект, который представляет желаемое комплексное число, и возвращает на него ссылку. Каждый последующий вызов приведет к немедленному завершению функции и возврату результата, вычисленного в первый раз. Что касается эффективности, то обращение к i во второй, третий и т.д. раз должно отнимать времени ненамного больше, чем операция доступа к атрибуту.
Результат, найденный при первом вызове однократной функции, может использоваться во всех экземплярах класса, включая экземпляры потомков, где эта функция не переопределена. Переопределение однократных функций как обычных (и обычных как однократных) допускается без всяких ограничений. Так, если COMPLEX1, порожденный от класса COMPLEX, заново определяет i, то обращение к i в экземпляре COMPLEX1 означает вызов переопределенного варианта, а обращение к i в экземпляре самого COMPLEX или его потомка, отличного от COMPLEX1, означает вызов однократной функции, то есть значения, найденного ею при первом вызове.