Шаблон проектирования для механизма отмены

StackOverflow https://stackoverflow.com/questions/49755

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

Если вы говорите о ГоФ, то Сувенир шаблон специально предназначен для отмены.

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

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

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

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

Возможно, вы захотите обратиться к Краски.ЧИСТЫЙ код что касается их отмены - у них действительно хорошая система отмены.Вероятно, это немного проще, чем то, что вам нужно, но это может дать вам некоторые идеи и рекомендации.

-Адам

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

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

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

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

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

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

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

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

Подробная информация здесь

Я с тобой Mendelt Siebenga о том, что вы должны использовать Командный Шаблон.Шаблон, который вы использовали, был шаблоном Memento, который со временем может стать и будет становиться очень расточительным.

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

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

Проект Codeplex:

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

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

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

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

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

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

Было много ошибок из-за указателей (C ++) на объекты, которые никогда не были исправлены, поскольку вы выполняете некоторые нечетные последовательности отмены повтора (эти места не обновлены до более безопасных ”идентификаторов", поддерживающих отмену).Ошибки в этой области часто ... ммм...интересно.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

На мой взгляд, ОТМЕНА / ПОВТОР могли бы быть реализованы в широком смысле двумя способами.1.Уровень команд (называемый уровнем отмены/повтора команд) 2.Уровень документа (называемый глобальной отменой/повтором)

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top