Шаблон проектирования для отмены движка - PullRequest
114 голосов
/ 08 сентября 2008

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

Я уже кодировал механизм отмены, который сохраняет глубокую копию после каждой модификации модели. Теперь я начал думать, мог бы я по-другому кодировать. Вместо сохранения глубоких копий, я мог бы сохранить список каждого действия модификатора с соответствующим обратным модификатором. Чтобы я мог применить обратные модификаторы к текущей модели, чтобы отменить, или модификаторы, чтобы повторить.

Я могу представить, как вы выполняете простые команды, которые изменяют свойства объекта и т. Д. Но как насчет сложных команд? Как вставка новых объектов узлов в модель и добавление некоторых линейных объектов, которые сохраняют ссылки на новые узлы.

Как можно реализовать это?

Ответы [ 22 ]

2 голосов
/ 08 сентября 2008

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

2 голосов
/ 05 сентября 2014

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

Эта концепция не очень популярна, но четко определена и полезна. Если определение выглядит слишком абстрактным для вас, этот проект является успешным примером того, как операционное преобразование для объектов JSON определено и реализовано в Javascript

2 голосов
/ 19 марта 2009

Для справки, вот простая реализация шаблона Command для Undo / Redo в C #: Простая система отмены / возврата для C # .

1 голос
/ 08 сентября 2008

Мы повторно использовали файл загрузки и сохраняем код сериализации для «объектов» для удобной формы для сохранения и восстановления всего состояния объекта. Мы помещаем эти сериализованные объекты в стек отмены вместе с некоторой информацией о том, какая операция была выполнена, и намекаем на отмену этой операции, если из сериализованных данных не было получено достаточно информации. Отмена и повторное выполнение часто просто заменяют один объект другим (теоретически).

Было много МНОГИХ ошибок из-за указателей (C ++) на объекты, которые никогда не исправлялись, когда вы выполняете некоторые нечетные последовательности повторов отмены (те места, которые не обновляются, чтобы более безопасные «идентификаторы отмены отмены»). Ошибки в этой области часто ... ммм ... интересные.

Некоторые операции могут быть особыми случаями для скорости / использования ресурсов - например, определение размеров, перемещение объектов.

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

1 голос
/ 08 сентября 2008

Я должен был сделать это, когда писал решатель для головоломки-прыжка. Я делал каждое перемещение объекта Command, который содержал достаточно информации, чтобы его можно было либо сделать, либо отменить. В моем случае это было так же просто, как сохранить начальную позицию и направление каждого движения. Затем я сохранил все эти объекты в стеке, чтобы программа могла легко отменить столько ходов, сколько необходимо при возврате.

1 голос
/ 04 мая 2016

Вы можете попробовать готовую реализацию шаблона Undo / Redo в PostSharp. https://www.postsharp.net/model/undo-redo

Позволяет добавлять функции отмены / повтора в ваше приложение без самостоятельной реализации шаблона. Он использует шаблон Recordable для отслеживания изменений в вашей модели и работает с шаблоном INotifyPropertyChanged, который также реализован в PostSharp.

Вам предоставляются элементы управления пользовательским интерфейсом, и вы можете решить, каким будет название и степень детализации каждой операции.

0 голосов
/ 13 марта 2018

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

Этот подход кажется разумным, если вам нужны небольшие усилия по реализации и простота обслуживания (и вы можете позволить себе дополнительную память для второго экземпляра).

Смотрите здесь пример: https://github.com/thilo20/Undo/

0 голосов
/ 26 июля 2016

Вы можете сделать свою первоначальную идею эффективной.

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

0 голосов
/ 06 мая 2016

По моему мнению, UNDO / REDO может быть реализовано двумя способами в широком смысле. 1. Командный уровень (называется командный уровень Undo / Redo) 2. Уровень документа (называемый глобальным Undo / Redo)

Уровень команд: как указывают многие ответы, это эффективно достигается с помощью шаблона Memento. Если команда также поддерживает журналирование действия, повторное выполнение легко поддерживается.

Ограничение: Как только область действия команды вышла, отмена / повтор невозможна, что приводит к уровню документа (глобальному) отмена / повтор

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

  1. Отмена всей памяти / повтор
  2. Уровень объекта Отменить Повторить

В «Отмена / восстановление всей памяти» вся память обрабатывается как связанные данные (например, дерево, или список, или график), а память управляется приложением, а не ОС. Поэтому операторы new и delete в C ++ перегружены, чтобы содержать более конкретные структуры для эффективной реализации таких операций, как a. Если какой-либо узел изменен, б. хранение и очистка данных и т. д., Он работает в основном для копирования всей памяти (при условии, что распределение памяти уже оптимизировано и управляется приложением с использованием расширенных алгоритмов) и сохранения его в стеке. Если запрашивается копия памяти, древовидная структура копируется на основе необходимости иметь неглубокую или глубокую копию. Глубокая копия сделана только для той переменной, которая изменена. Поскольку каждая переменная выделяется с помощью пользовательского выделения, приложение имеет последнее слово, когда нужно удалить ее, если это необходимо. Вещи становятся очень интересными, если мы должны разделить Undo / Redo, когда это происходит, когда нам нужно программно-выборочно Undo / Redo набор операций. В этом случае только этим новым переменным, или удаленным переменным, или измененным переменным присваивается флаг, так что Undo / Redo только отменяет / восстанавливает эту память Вещи становятся еще более интересными, если нам нужно сделать частичную отмену / возврат внутри объекта. Когда это так, используется более новая идея «Шаблон посетителя». Он называется «Отменить / повторить уровень объекта»

  1. Уровень объекта Отменить / Повторить: Когда вызывается уведомление об отмене / повторе, каждый объект реализует потоковую операцию, в которой стример получает от объекта старые данные / новые данные, которые запрограммированы. Данные, которые не подлежат нарушению, остаются без изменений. Каждый объект получает в качестве аргумента стример, и внутри вызова UNDo / Redo он направляет / выводит данные объекта.

И 1, и 2 могут иметь такие методы, как 1. BeforeUndo () 2. AfterUndo () 3. BeforeRedo () 4. AfterRedo (). Эти методы должны быть опубликованы в базовой команде Undo / redo (не в контекстной команде), чтобы все объекты реализовали эти методы, чтобы получить определенное действие.

Хорошая стратегия - создать гибрид из 1 и 2. Прелесть в том, что эти методы (1 и 2) сами используют шаблоны команд

0 голосов
/ 08 сентября 2008

Однажды я работал над приложением, в котором все изменения, внесенные командой в модель приложения (т. Е. CDocument ... мы использовали MFC), сохранялись в конце команды путем обновления полей во внутренней базе данных, поддерживаемой в пределах модель. Поэтому нам не нужно было писать отдельный код отмены / повтора для каждого действия. Стек отмены просто запоминал первичные ключи, имена полей и старые значения при каждом изменении записи (в конце каждой команды).

...