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, означает вызов однократной функции, то есть значения, найденного ею при первом вызове.
Применение однократных подпрограмм
Понятие однократных подпрограмм расширяет круг задач, позволяя включить разделяемые объекты, глобальные системные параметры, инициализацию общих свойств.
Для ссылочных типов, таких как COMPLEX, наш механизм фактически предлагает константные ссылки, а не обязательно константные объекты. Он гарантирует, что тело функции выполняется при первом обращении, возвращая результат, который будет также возвращаться при последующих вызовах, уже не требуя никаких действий.
Если функция возвращает значение ссылочного типа, то в ее теле, как правило, есть инструкция создания объекта, и любой вызов приведет к получению ссылки на этот объект. Хотя создание объекта не повторяется, ничто не мешает изменить сам объект, воспользовавшись полученной ссылкой. В итоге мы имеем разделяемый объект, не являющийся константным.
Пример такого объекта - окно вывода информации об ошибках. Пусть все компоненты интерактивной системы могут направлять в это окно свои сообщения:
Message_window.put_text ("Соответствующее сообщение об ошибке")
где Message_window имеет тип WINDOW, чей класс описан следующим образом:
class WINDOW
creation
make
feature
make (...) is
-- Создать окно; аргументы задают размер и положение.
do ... end
text: STRING
-- Отображаемый в окне текст
put_text (s: STRING) is
-- Сделать s отобржаемым в окне текстом.
do
text := s
end
... Прочие компоненты ...
end -- класс WINDOW
Ясно, что объект Message_window должен быть одним для всех компонентов системы. Это достигается описанием соответствующего компонента как однократной функции:
Message_window: WINDOW is
-- Окно для вывода сообщений об ошибках
once
create Result.make ("... Аргументы размера и положения ...")
end
В данном случае окно сообщений должно находиться в совместном пользовании всех сторон, но не являться константным объектом. Каждый вызов put_text будет изменять объект, помещая в него новую строку текста. Лучшим местом описания Message_window станет класс, от которого порождены все компоненты системы, нуждающиеся в окне выдачи сообщений.
Создав разделяемый объект, играющий роль константы, (например,
i), вы можете запретить вызовы
i.some_procedure, способные его изменять. Для этого, например, в классе
COMPLEX достаточно ввести в инвариант класса предложения
i.x = 0 и
i.y = 1.
Однократные функции с результатами базовых типов
Еще одним применением однократных функций является моделирование глобальных значений - "системных параметров", которые обычно нужны сразу нескольким классам, но не меняются в ходе программной сессии. Их начальная установка требует информации от пользователя или операционной среды. Например:
[x]. компонентам низкоуровневой системы может понадобиться объем доступной им памяти, выделенный средой при инициализации;
[x]. система эмуляции терминала может начать работу с отправки среде запроса о числе терминальных портов. Затем эти данные будут использоваться в ряде модулей приложения.
Такие глобальные данные аналогичны совместно используемым объектам, хотя обычно они являются значениями базовых типов. Схема их реализации однократными функциями такова:
Const_value: T is
-- Однократно вычисляемый системный параметр
local
envir_param: T ' -- Любой тип (T и не только)
once
"Получить envir_param из операционной среды"
Result := "Значение, рассчитанное на основе envir_param"
end
Такие однократные функции описывают динамически вычисляемые константы.
Предположим, данное объявление находится в классе ENVIR. Класс, которому надо воспользоваться константой Const_value, получит ее значение, указав ENVIR в списке своих родителей. В отличие от классического подхода к расчету константы, здесь не нужна процедура инициализации системы, вычисляющая все глобальные параметры системы, как это делается в классическом подходе. Как отмечалось в начальных лекциях, такая процедура должна была бы иметь доступ к внутренним деталям многих модулей, что нарушало бы ряд критериев и принципов модульности: декомпозиции, скрытия информации и других. Наоборот, классы, подобные ENVIR, могут разрабатываться как согласованные модули, каждый задающий множество логически связанных глобальных значений. Процесс вычисления такого параметра, к примеру, Const_value, инициирует первый из компонентов, который запросит этот параметр при выполнении системы. Хотя Const_value является функцией, использующие его компоненты могут полагать, что имеют дело с константным атрибутом.