Предвычисленные командные объекты
Еще до выполнения команды следует получить, а иногда и создать соответствующий командный объект. Для абстрактно написанной инструкции "Создать подходящий командный объект и присоединить его к requested " была предложена схема реализации:
inspect
request_code
when Line_insertion then
create {LINE_INSERTION} requested.make (...)
и т.д. (одна ветвь для каждого типа команды)
Как отмечалось, здесь нет нарушения принципа Единственного Выбора: фактически это и есть точка выбора - единственное место в системе, знающее, какое множество команд поддерживается. Но к этому времени у нас выработалось здоровое отвращение к инструкциям if или inspect, содержащим много ветвей. Давайте попытаемся избавиться от них, хотя их присутствие кажется на первый взгляд неизбежным.
Мы создадим широко применимый образец проектирования, который может быть назван множество предвычисленных полиморфных экземпляров (precomputing a polymorphic instance set).
Идея достаточно проста - создать раз и навсегда полиморфную структуру данных, содержащую по одному экземпляру каждого варианта, затем, когда нужен новый объект, просто получаем его из соответствующего входа в структуру.
Хотя для этого возможны различные структуры, например списки, мы будем использовать массив ARRAY [COMMAND], позволяющий идентифицировать каждый тип команды целым в интервале 1 и до command_count - числом типов команд. Объявим:
commands: ARRAY [COMMAND]
и инициализируем его элементы так, чтобы i -й элемент ( 1 <= i <= n ) ссылался на экземпляр класса потомка COMMAND, соответствующего коду i ; например, создадим экземпляр LINE_DELETION, свяжем его с первым элементом массива, так что удаление строки будет иметь код 1.
Рис. 3.5.Массив шаблонов команд
Подобная техника может быть применена к полиморфному массиву associated_state, используемому в ОО-решении предыдущей лекции для приложения, управляемого панелями. |
Массив commands дает еще один пример мощи полиморфных структур данных. Его инициализация тривиальна:
create commands.make (1, command_count)
create {LINE_INSERTION} requested.make; commands.put (requested, 1)
create {STRING_REPLACE} requested.make; commands.put (requested, 2)
... И так для каждого типа команд ...
Заметьте, при этом подходе процедуры создания не должны иметь аргументов; если командный класс имеет атрибуты, то следует устанавливать их значения позднее в специально написанных процедурах, например li.make (input_text, cursor_position), где li типа LINE_INSERTION.
Теперь исчезла необходимость применения разбора случаев и ветвящихся инструкций if или inspect. Приведенная выше инициализация служит теперь точкой Единственного Выбора. Теперь реализацию абстрактной операции "Создать подходящий командный объект и присоединить его к requested " можно записать так:
requested := clone (commands @ code)
где code является кодом последней команды. Так как каждый тип команды имеет теперь код, соответствующий его индексу в массиве, то базисная операция интерфейса, ранее написанная в виде "Декодировать запрос", анализирует запрос пользователя и определяет соответствующий код.
В присваивании requested используется клон ( clone ) шаблона команды из массива, так что можно получать более одного экземпляра одной и той же команды в списке истории (как это показано в предыдущем примере, где в списке истории присутствовали два экземпляра LINE_DELETION ).
Если, однако, использовать предложенную технику, полностью отделяющую аргументы команды от командных объектов (так что список истории содержит экземпляры COMMAND_INSTANCE, а не COMMAND ), то тогда в получении клонов нет необходимости, и можно перейти к использованию ссылок на оригинальные объекты из массива:
requested:= commands @ code
В длительных сессиях такая техника может давать существенный выигрыш.