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

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

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

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

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

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

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

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

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

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

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

    Если вы говорите о GoF, шаблон Memento специально обращается к отмене.

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

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

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

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

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

    -Адам

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

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

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

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

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

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

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

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

    больше информации здесь

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

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

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

    Проект Codeplex :

    Это простая структура для добавления функциональности Undo / Redo в ваши приложения на основе classического шаблона проектирования команд. Он поддерживает операции слияния, вложенные транзакции, задержку выполнения (выполнение транзакции на верхнем уровне транзакции) и возможную нелинейную историю отмены (где вы можете выбрать несколько повторных действий для повтора).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. Вся память отменена / повторена
    2. Уровень отмены Undo Redo

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

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

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

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

    Давайте будем гением компьютера.