Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4087 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 3:

Наследование: "откат" в интерактивных системах

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >

Многоуровневый откат и повтор: undo и redo

В некоторых системах откат ограничен одним уровнем. Если не делать двух ошибок подряд, то этого достаточно. Но если вы пошли не по той дороге и хотите вернуться назад, то нужен многоуровневый откат.

Нет никаких причин ограничиваться одним уровнем. Как только механизм отката разработан, распространение его на несколько уровней не представляет особого труда, что и будет показано в этой лекции. И, пожалуйста, говорю уже как потенциальный пользователь, не ограничивайте число уровней отката, а если уж вынуждены это сделать, то пусть ограничение задает сам пользователь, во всяком случае по умолчанию оно должно быть не меньше 20. Затраты на откат невелики, если применять описываемую здесь технику.

Многоуровневый откат может быть избыточным. Потому необходима независимая операция повтора Redo, в которой нет нужды, если откат ограничен одним шагом.

Практические проблемы

Хотя при разумных усилиях механизм undo-redo может быть встроен в любую хорошо написанную ОО-систему, лучше всего с самого начала планировать его использование. На архитектуре ПО это скажется введением класса command, что может и не придти в голову, если не думать об откате при проектировании системы.

Практичный механизм undo-redo требует учета нескольких требований. Это свойство следует включить в интерфейс пользователя. Для начала можно полагать, что множество доступных операций обогащено двумя новыми командами: Undo и Redo. (Для них может быть введена соответствующая комбинация горячих клавиш, например control-U и control-R.) Команда Undo отменяет эффект последней еще не отмененной команды; Redo повторно выполняет команду, отмененную при откате. Следует определить соглашения для попыток отката на большее число шагов, чем их было сделано первоначально, при попытках повтора, когда не было отката, - такие запросы можно игнорировать или выдавать предупреждающее сообщение. (Таков возможный взгляд на интерфейс пользователя, поддерживающий undo-redo. В конце лекции мы увидим, что возможен лучший вариант интерфейса.)

Второе, что следует учитывать, действия не всех команд могут быть отменены. В некоторых ситуациях этого нельзя сделать фактически: после выполнения команды "запуск ракет" (которую может отдать, как известно, лишь президент) или менее драматичной "отпечатать страницу" действия этих команд необратимы. В других ситуациях эффект от действия команды может быть устранен, но ценой слишком больших усилий. Например, текстовые редакторы, как правило, не позволяют отменить действия команды Save, записывающей текущее состояние документа в файл. Реализация отката должна учитывать наличие таких необратимых команд, четко указывая их статус в интерфейсе пользователя. Ограничивайте необратимые команды случаями, для которых оправдание свойства может быть сформулировано в терминах пользователя.

Контрпримером является часто используемое мной приложение, обрабатывающее документы, которое изредка сообщает, что запрашиваемую команду нельзя отменить, хотя причины этого ясны только самой программе.

Интересно, что в каком-то смысле это утверждение ложно, - фактически вы можете отменить эффект команды, но не через Undo, а через команду "Вернуться к последней сохраненной версии документа". Это наблюдение приводит к следующему правилу: всякий раз, когда команду законно следует признать необратимой, не поступайте, как в приведенном выше примере, выводя сообщение "Эта команда будет необратимой". Вместо выбора двух возможностей - Continue anyway и Cancel - предоставьте пользователю три: сохранить документ и затем выполнить команду, выполнить без сохранения, отмена команды.

Наконец, можно попытаться предложить общую схему "Undo, Skip, Redo", позволяющую после нескольких операций Undo пропустить некоторые команды перед включением Redo. Интерфейс пользователя, показанный в конце этой лекции, поддерживает такое расширение, но возникают концептуальные проблемы: после пропуска некоторых команд может оказаться невозможным выполнить следующую команду. Рассмотрим тривиальный пример текстового редактора и сессию некоторого пользователя с набранной одной строкой текста. Предположим, пользователь выполнил две команды:

(1) Добавить строку в конец текста.
(2) Удалить вторую строку.

После отмены обеих команд пользователь захотел пропустить выполнение первой и повторно выполнить только вторую (skip (1) и redo (2)). К несчастью, в этом состоянии выполнение команды (2) бессмысленно, поскольку нет второй строки. Эта проблема не столько интерфейса, сколько реализации: команда "Удалить вторую строку" была применима к структуре объекта, полученного в результате выполнения команды (1), ее применение к структуре, предшествующей выполнению (1), может быть невозможным или приводить к непредсказуемым результатам.

Требования к решению

Механизм undo-redo, который мы намереваемся обеспечить, должен удовлетворять следующим свойствам:

  • U1 Механизм должен быть применим к широкому классу интерактивных приложений независимо от их проблемной области.
  • U2 Механизм не должен требовать перепроектирования при добавлении новых команд.
  • U3 Он должен разумно использовать ресурсы памяти.
  • U4 Он должен быть применимым к откатам как на один, так и несколько уровней.

Первое требование следует из того, что ничего проблемно-специфического в откатах и повторах нет. Только для облегчения обсуждения мы будем использовать в качестве примера знакомый каждому инструмент - текстовый редактор, (подобный Notepad или Vi), позволяющий пользователям вводить тексты и выполнять такие команды, как: INSERT_LINE, DELETE_LINE, GLOBAL_REPLACEMENT (одного слова в тексте другим) и другие. Но это только пример, и ни одна из концепций, обсуждаемых ниже, не является характерной только для текстовых редакторов.

Второе требование означает, что Undo и Redo имеют особый статус и не могут рассматриваться подобно любым другим командам интерактивной системы. Будь Undo обычной командой, ее структура требовала бы разбора случаев в форме:

If "Последняя команда была INSERT_LINE" then
    "Undo эффект INSERT_LINE"
elseif "Последняя команда была DELETE_LINE" then
    "Undo эффект DELETE_LINE"
и т.д.

Мы знаем (см. лекцию 3 курса "Основы объектно-ориентированного программирования"), как плохи такие структуры, противоречащие принципу Единственного Выбора и затрудняющие расширяемость системы. Пришлось бы изменять программный текст при всяком добавлении новой команды. Хуже того, код каждой ветви отражал бы код соответствующей команды, например, первая ветвь должна бы знать достаточно много о том, что делает команда INSERT_LINE. Это было бы свидетельством изъянов проекта.

Третье требование заставляет нас бережно относиться к памяти. Понятно, что механизм undo-redo требует хранения некоторой информации для каждой команды Undo: например, при выполнении DELETE_LINE, нет возможности выполнить откат, если перед выполнением команды не запомнить где-нибудь удаляемую строку и ее позицию в тексте. Но следует хранить только то, что логически необходимо.

Вследствие третьего требования исключается такое очевидное решение, как сохранение полного состояния системы перед выполнением каждой команды. Такое решение можно было бы тривиально написать, используя свойства STORABLE (см.лекцию 8 курса "Основы объектно-ориентированного программирования"), но оно было бы нереалистичным, так как просто пожирало бы память. Нужно придумать что-то более разумное.

Последнее требование поддержки произвольного числа уровней отката уже обсуждалось. В данном случае оказывается проще рассмотреть откат на один уровень и затем обобщить решение на произвольное число уровней.

Этими требованиями заканчивается презентация проблемы. Хорошей идеей, как обычно, является попытка самостоятельного поиска решения, прежде чем продолжить чтение этой лекции.

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >