Я пишу инструмент структурного моделирования для приложения гражданского строительства. У меня есть один огромный класс модели, представляющий все здание, которое включает коллекции узлов, линейных элементов, нагрузок и т. Д., Которые также являются настраиваемыми классами.
Я уже написал механизм отмены, который сохраняет полную копию после каждой модификации модели. Теперь я задумался, можно ли было кодировать иначе. Вместо того, чтобы сохранять глубокие копии, я мог бы, возможно, сохранить список каждого действия модификатора с соответствующим модификатором реверсирования. Чтобы я мог применить модификаторы реверса к текущей модели для отмены или модификаторы для повтора.
Я могу представить, как бы вы выполняли простые команды, изменяющие свойства объекта и т. Д. Но как насчет сложных команд? Например, вставка новых узловых объектов в модель и добавление некоторых линейных объектов, которые сохраняют ссылки на новые узлы.
Как бы это реализовать?
источник
Ответы:
В большинстве примеров, которые я видел, используется вариант Command-Pattern . Каждое отменяемое действие пользователя получает свой собственный экземпляр команды со всей информацией для выполнения действия и его отката. Затем вы можете вести список всех выполненных команд и откатывать их одну за другой.
источник
Я думаю, что и сувенир, и команда непрактичны, когда вы имеете дело с моделью того размера и объема, которые подразумевает OP. Они будут работать, но потребуется много работы по поддержанию и расширению.
Я думаю, что для этого типа проблемы вам необходимо встроить поддержку вашей модели данных, чтобы поддерживать дифференциальные контрольные точки для каждого объекта, участвующего в модели. Я сделал это однажды, и это сработало очень гладко. Самое важное, что вам нужно сделать, - это избегать прямого использования указателей или ссылок в модели.
Каждая ссылка на другой объект использует некоторый идентификатор (например, целое число). Когда объект нужен, вы ищите текущее определение объекта в таблице. Таблица содержит связанный список для каждого объекта, который содержит все предыдущие версии, а также информацию о том, для какой контрольной точки они были активны.
Реализовать отмену / повтор просто: выполните свое действие и установите новую контрольную точку; откатить все версии объекта к предыдущей контрольной точке.
Это требует некоторой дисциплины в коде, но имеет много преимуществ: вам не нужны глубокие копии, поскольку вы делаете дифференциальное хранение состояния модели; вы можете ограничить объем памяти, который хотите использовать (что очень важно для таких вещей, как модели САПР), либо количеством повторов, либо использованной памятью; очень масштабируемые и простые в обслуживании для функций, которые работают с моделью, поскольку им не нужно ничего делать для реализации отмены / повтора.
источник
Если вы говорите о GoF, шаблон Memento специально предназначен для отмены.
источник
Как утверждали другие, шаблон команды - очень мощный метод реализации отмены / возврата. Но есть важное преимущество, которое я хотел бы отметить в шаблоне команд.
При реализации отмены / повтора с использованием шаблона команд вы можете избежать большого количества дублированного кода, абстрагируя (в определенной степени) операции, выполняемые с данными, и используя эти операции в системе отмены / повтора. Например, в текстовом редакторе вырезание и вставка являются дополнительными командами (помимо управления буфером обмена). Другими словами, операция отмены для вырезания - это вставка, а операция отмены для вставки - это вырезка. Это относится к гораздо более простым операциям, таким как ввод и удаление текста.
Ключевым моментом здесь является то, что вы можете использовать свою систему отмены / повтора в качестве основной системы команд для вашего редактора. Вместо того, чтобы писать систему, такую как «создать объект отмены, изменить документ», вы можете «создать объект отмены, выполнить операцию повтора для объекта отмены, чтобы изменить документ».
Сейчас, по общему признанию, многие люди думают про себя: «Ну да, разве не является частью шаблона команды?» Да, но я видел слишком много систем команд, которые имеют два набора команд: один для немедленных операций, а другой - для отмены / повтора. Я не говорю, что не будет команд, специфичных для немедленных операций и отмены / повтора, но уменьшение дублирования сделает код более удобным в сопровождении.
источник
paste
какcut
^ -1.Вы можете обратиться к коду Paint.NET для их отмены - у них действительно хорошая система отмены. Это, вероятно, немного проще, чем то, что вам нужно, но может дать вам некоторые идеи и рекомендации.
-Адам
источник
Это может быть тот случай, когда применим CSLA . Он был разработан для обеспечения комплексной поддержки отмены для объектов в приложениях Windows Forms.
источник
Я успешно реализовал сложные системы отмены, используя шаблон Memento - очень просто, и преимущество также заключается в естественном предоставлении инфраструктуры Redo. Более тонкое преимущество заключается в том, что совокупные действия также могут содержаться в одной отмене.
Короче говоря, у вас есть две стопки памятных предметов. Один для отмены, другой для возврата. Каждая операция создает новый сувенир, который в идеале должен представлять собой несколько вызовов для изменения состояния вашей модели, документа (или чего-то еще). Он добавляется в стек отмены. Когда вы выполняете операцию отмены, помимо выполнения действия Undo для объекта Memento, чтобы снова изменить модель, вы также выталкиваете объект из стека Undo и помещаете его прямо в стек Redo.
Как реализован метод изменения состояния вашего документа, полностью зависит от вашей реализации. Если вы можете просто выполнить вызов API (например, ChangeColour (r, g, b)), то перед ним укажите запрос, чтобы получить и сохранить соответствующее состояние. Но шаблон также будет поддерживать создание глубоких копий, снимков памяти, создание временных файлов и т. Д. - все зависит от вас, поскольку это просто реализация виртуального метода.
Для выполнения совокупных действий (например, пользователь Shift-выбирает загрузку объектов для выполнения операции, например, удаление, переименование, изменение атрибута), ваш код создает новый стек отмены как единый памятный знак и передает его фактической операции в добавить отдельные операции в. Таким образом, вашим методам действий не нужно (а) иметь глобальный стек, о котором нужно беспокоиться, и (б) могут быть закодированы одинаково, независимо от того, выполняются ли они изолированно или как часть одной агрегированной операции.
Многие системы отмены находятся только в памяти, но я думаю, вы можете сохранить стек отмены, если хотите.
источник
Только что прочитал о шаблоне команд в моей книге по гибкой разработке - может быть, у него есть потенциал?
Вы можете заставить каждую команду реализовывать командный интерфейс (который имеет метод Execute ()). Если вы хотите отменить, вы можете добавить метод отмены.
подробнее здесь
источник
Я поддерживаю Мендельта Зибенга в том, что вам следует использовать командный шаблон. Вы использовали шаблон Memento Pattern, который со временем может стать очень расточительным.
Поскольку вы работаете с приложением, интенсивно использующим память, вы должны иметь возможность указать, сколько памяти может занимать механизм отмены, сколько уровней отмены сохраняется, или какое-то хранилище, в котором они будут сохраняться. Если вы этого не сделаете, вы скоро столкнетесь с ошибками, связанными с нехваткой памяти на машине.
Я бы посоветовал вам проверить, есть ли фреймворк, который уже создал модель для отмены на выбранном вами языке программирования / фреймворке. Придумывать новое приятно, но лучше взять что-то уже написанное, отлаженное и протестированное на реальных сценариях. Было бы полезно, если бы вы добавили то, на чем пишете, чтобы люди могли рекомендовать знакомые им фреймворки.
источник
Проект Codeplex :
Это простой фреймворк для добавления функциональности Undo / Redo в ваши приложения, основанный на классическом шаблоне проектирования Command. Он поддерживает действия слияния, вложенные транзакции, отложенное выполнение (выполнение при фиксации транзакции верхнего уровня) и возможную нелинейную историю отмены (где вы можете выбрать несколько действий для повторения).
источник
В большинстве примеров, которые я читал, это делается с использованием либо команды, либо шаблона сувенира. Но вы можете сделать это и без шаблонов проектирования с помощью простой структуры deque .
источник
Умный способ обработки отмены, который сделал бы ваше программное обеспечение также подходящим для многопользовательской совместной работы, - это внедрение операционной трансформации. структуры данных.
Эта концепция не очень популярна, но четко определена и полезна. Если определение кажется вам слишком абстрактным, этот проект является успешным примером того, как оперативное преобразование для объектов JSON определяется и реализуется в Javascript.
источник
Для справки, вот простая реализация паттерна Command для Undo / Redo на C #: Простая система отмены / redo для C # .
источник
Мы повторно использовали загрузку файла и код сериализации для «объектов» в удобной форме для сохранения и восстановления всего состояния объекта. Мы помещаем эти сериализованные объекты в стек отмены - вместе с некоторой информацией о том, какая операция была выполнена, и намеками на отмену этой операции, если из сериализованных данных извлечено недостаточно информации. Отмена и возврат часто просто заменяют один объект другим (теоретически).
Было МНОЖЕСТВО ошибок из-за указателей (C ++) на объекты, которые никогда не исправлялись, поскольку вы выполняете некоторые странные последовательности отмены повторения (те места, которые не обновлялись для более безопасных «идентификаторов», учитывающих отмену). Баги в этой области часто ... ммм ... интересно.
Некоторые операции могут быть особыми случаями использования скорости / ресурсов - например, определение размеров, перемещение предметов.
Множественный выбор также дает некоторые интересные сложности. К счастью, у нас уже была концепция группировки в коде. Комментарий Кристофера Джонсона о подпунктах довольно близок к тому, что мы делаем.
источник
Мне пришлось сделать это при написании решателя для игры-головоломки с прыжками от колышка. Я делал каждое движение командным объектом, который содержал достаточно информации, чтобы его можно было сделать или отменить. В моем случае это было так же просто, как сохранение начальной позиции и направления каждого движения. Затем я сохранил все эти объекты в стеке, чтобы программа могла легко отменить столько ходов, сколько нужно, при возврате.
источник
Вы можете попробовать готовую реализацию паттерна Undo / Redo в PostSharp. https://www.postsharp.net/model/undo-redo
Он позволяет добавлять в приложение функции отмены / повтора действий без самостоятельной реализации шаблона. Он использует шаблон Recordable для отслеживания изменений в вашей модели и работает с шаблоном INotifyPropertyChanged, который также реализован в PostSharp.
Вам предоставляются элементы управления пользовательского интерфейса, и вы можете решить, каким будет имя и степень детализации каждой операции.
источник
Однажды я работал над приложением, в котором все изменения, внесенные командой в модель приложения (например, CDocument ... мы использовали MFC), сохранялись в конце команды путем обновления полей во внутренней базе данных, поддерживаемой в модели. Таким образом, нам не пришлось писать отдельный код отмены / повтора для каждого действия. Стек отмены просто запоминал первичные ключи, имена полей и старые значения при каждом изменении записи (в конце каждой команды).
источник
В первом разделе паттернов проектирования (GoF, 1994) описан вариант использования отмены / повтора в качестве шаблона проектирования.
источник
Вы можете воплотить свою первоначальную идею в жизнь.
Используйте постоянные структуры данных и придерживайтесь списка ссылок на старое состояние . (Но это действительно работает только в том случае, если операции все данные в вашем классе состояния неизменяемы, и все операции с ним возвращают новую версию --- но новая версия не должна быть глубокой копией, просто замените копию измененных частей -он-запись».)
источник
Я обнаружил, что здесь очень полезен шаблон Command. Вместо реализации нескольких обратных команд я использую откат с отложенным выполнением на втором экземпляре моего API.
Этот подход кажется разумным, если вам нужны небольшие усилия по реализации и простота обслуживания (и вы можете позволить себе дополнительную память для второго экземпляра).
См. Пример здесь: https://github.com/thilo20/Undo/
источник
Я не знаю, будет ли это полезно для вас, но когда мне пришлось сделать что-то подобное в одном из моих проектов, я в конечном итоге загрузил UndoEngine с http://www.undomadeeasy.com - замечательный движок и меня действительно не особо заботило, что находится под капотом - это просто работало.
источник
На мой взгляд, UNDO / REDO можно реализовать двумя способами. 1. Командный уровень (называемый командным уровнем Отменить / Вернуть) 2. Уровень документа (называемый глобальным Отменить / Вернуть)
Командный уровень: как отмечается во многих ответах, это эффективно достигается с помощью шаблона Memento. Если команда также поддерживает ведение журнала действия, повторение легко поддерживается.
Ограничение: как только область действия команды исчерпана, отменить / повторить невозможно, что приводит к отмене / повторному выполнению на уровне документа (глобальном).
Я предполагаю, что ваш случай вписался бы в глобальную отмену / повтор, поскольку он подходит для модели, которая требует много места в памяти. Кроме того, это также подходит для выборочной отмены / повтора. Есть два примитивных типа
В режиме «Отменить / повторить всю память» вся память рассматривается как связанные данные (такие как дерево, список или граф), а память управляется приложением, а не ОС. Таким образом, операторы new и delete, если в C ++ перегружены, чтобы содержать более конкретные структуры для эффективной реализации таких операций, как. Если какой-либо узел изменен, b. хранение и очистка данных и т. д. Принцип его работы заключается в том, чтобы скопировать всю память (при условии, что выделение памяти уже оптимизировано и управляется приложением с использованием передовых алгоритмов) и сохранить ее в стеке. Если запрашивается копия памяти, древовидная структура копируется в зависимости от необходимости иметь мелкую или глубокую копию. Глубокая копия делается только для той переменной, которая изменена. Поскольку каждая переменная выделяется с использованием настраиваемого распределения, последнее слово остается за приложением, когда его удалять, если это необходимо. Вещи становятся очень интересными, если нам нужно разделить Undo / Redo, когда случается так, что нам нужно программно выборочно отменить / Redo набор операций. В этом случае только эти новые переменные, или удаленные переменные, или измененные переменные получают флаг, так что Undo / Redo отменяет / повторяет только эту память. Вещи становятся еще более интересными, если нам нужно выполнить частичную Undo / Redo внутри объекта. В таком случае используется более новая идея «паттерна посетителя». Это называется «Отменить / повторить на уровне объекта». или удаленным переменным, или измененным переменным присваивается флаг, так что Undo / Redo отменяет / повторяет только эту память. Вещи становятся еще более интересными, если нам нужно выполнить частичную Undo / Redo внутри объекта. В таком случае используется более новая идея «паттерна посетителя». Это называется «Отменить / повторить на уровне объекта». или удаленным переменным, или измененным переменным присваивается флаг, так что Undo / Redo отменяет / повторяет только эту память. Вещи становятся еще более интересными, если нам нужно выполнить частичную Undo / Redo внутри объекта. В таком случае используется более новая идея «паттерна посетителя». Это называется «Отменить / повторить на уровне объекта».
И 1, и 2 могут иметь такие методы, как 1. BeforeUndo () 2. AfterUndo () 3. BeforeRedo () 4. AfterRedo (). Эти методы должны быть опубликованы в основной команде Undo / redo (а не в контекстной команде), чтобы все объекты также реализовали эти методы для получения определенного действия.
Хорошая стратегия - создать гибрид 1 и 2. Прелесть в том, что эти методы (1 и 2) сами используют шаблоны команд.
источник