Шаблон дизайна для Undo Engine

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

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

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

Как бы это реализовать?

Ответ 1

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

Ответ 2

Я думаю, что и memento, и команда не практичны, когда вы имеете дело с моделью размера и объема, которые подразумевает OP. Они будут работать, но было бы много работы по поддержанию и расширению.

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

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

Реализация undo/redo проста: выполните свое действие и установите новую контрольную точку; откат всех версий объекта до предыдущей контрольной точки.

В коде требуется определенная дисциплина, но имеет много преимуществ: вам не нужны глубокие копии, так как вы выполняете дифференциальное хранение состояния модели; вы можете охватить объем памяти, который вы хотите использовать (очень важно для таких моделей, как модели САПР) либо количеством повторений, либо используемой памятью; очень масштабируемое и малое обслуживание для функций, которые работают с моделью, поскольку им не нужно ничего делать, чтобы реализовать отмену/повтор.

Ответ 3

Если вы говорите о GoF, шаблон Memento указывает на отмену.

Ответ 4

Как указывали другие, шаблон команды является очень мощным методом реализации Undo/Redo. Но есть важное преимущество, которое я хотел бы упомянуть в шаблоне команды.

При реализации undo/redo с использованием шаблона команды вы можете избежать большого количества дублированного кода, абстрагировав (до некоторой степени) операции, выполняемые с данными, и используйте эти операции в системе отмены/повтора. Например, в текстовом редакторе вырезать и вставлять дополнительные команды (кроме управления буфером обмена). Другими словами, операция отмены для разреза - это паста, и операция отмены для пасты разрезается. Это относится к гораздо более простым операциям, например, при вводе и удалении текста.

Ключ здесь состоит в том, что вы можете использовать свою систему отмены/повтора как основную систему команд для вашего редактора. Вместо того, чтобы писать систему, такую ​​как "создать объект отмены, изменить документ", вы можете "создать объект отмены, выполнить операцию повтора для объекта отмены, чтобы изменить документ".

Теперь, по общему признанию, многие люди думают про себя: "Ну, дух, это не часть точки шаблона команды?" Да, но я видел слишком много систем команд, которые имеют два набора команд: один для немедленных операций и другой набор для отмены/повтора. Я не говорю, что не будет команд, специфичных для немедленных операций и отмены/повтора, но уменьшение дублирования сделает код более удобным.

Ответ 5

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

-Adam

Ответ 6

Это может быть случай, когда применим CSLA. Он был разработан для обеспечения комплексной отмены поддержки объектов в приложениях Windows Forms.

Ответ 7

Я реализовал комплексные системы отмены, успешно используя шаблон Memento, - очень просто, и имеет преимущество, естественно, в создании среды Redo. Более тонкое преимущество заключается в том, что агрегированные действия могут содержаться и в одном отступлении.

В двух словах, у вас есть два пакета предметов памяти. Один для Undo, другой для Redo. Каждая операция создает новое напоминание, в идеале это будут некоторые призывы изменить состояние вашей модели, документа (или что-то еще). Это добавляется в стек отмены. Когда вы выполняете операцию отмены, помимо выполнения действия "Отменить" в объекте Memento, чтобы снова изменить модель, вы также выталкиваете объект из стека Undo и нажимаете его прямо на стек Redo.

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

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

Многие системы отмены находятся в памяти только, но вы можете сохранить стопку отмены, если хотите, я думаю.

Ответ 8

Просто читал о шаблоне команды в моей гибкой книге разработки - возможно, что получил потенциал?

Вы можете использовать каждую команду интерфейса команд (который имеет метод Execute()). Если вы хотите отменить, вы можете добавить метод отмены.

подробнее здесь

Ответ 9

Я с Mendelt Siebenga о том, что вы должны использовать Command Pattern. Образец, который вы использовали, это шаблон Memento, который может и будет очень расточительным с течением времени.

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

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

Ответ 11

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

Ответ 13

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

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

Ответ 14

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

Ответ 15

Мы повторно использовали загрузку файла и сохранили код сериализации для "объектов" для удобной формы для сохранения и восстановления всего состояния объекта. Мы выталкиваем эти сериализованные объекты в стек отмены, а также некоторую информацию о том, какая операция была выполнена, и намекает на отмену этой операции, если из сериализованных данных недостаточно информации. Undo and Redoing часто просто заменяет один объект другим (теоретически).

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

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

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

Ответ 16

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

Ответ 17

В первом разделе шаблонов проектирования (GoF, 1994) используется прецедент для реализации undo/redo в качестве шаблона проектирования.

Ответ 18

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

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

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

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

  • Отмена/повторная отмена памяти
  • Уровень отмены Undo Redo

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

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

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

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

Ответ 19

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

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

Ответ 20

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

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

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

Ответ 21

Я не знаю, будет ли это полезно для вас, но когда мне приходилось делать что-то подобное в одном из моих проектов, я закончил загрузку UndoEngine из http://www.undomadeeasy.com - замечательный движок, и мне действительно было все равно, что было под капотом - он просто сработал.