撤消引擎的设计模式

时间:2020-03-05 18:49:22  来源:igfitidea点击:

我正在为民用工程应用程序编写结构建模工具。我有一个代表整个建筑物的巨大模型类,其中包括节点,线元素,荷载等的集合,它们也是自定义类。

我已经编写了一个撤消引擎,该引擎在对模型进行每次修改后都可以保存一个深层副本。现在,我开始考虑是否可以使用其他代码进行编码。除了保存深层副本之外,我还可以保存每个修改器动作的列表以及相应的反向修改器。这样我可以将反向修改器应用于当前模型以撤消,或者将修改器应用于重做。

我可以想象我们将如何执行更改对象属性等的简单命令。但是复杂命令呢?就像在模型中插入新的节点对象并添加一些保留对新节点的引用的线对象一样。

如何实施呢?

解决方案

回答

我见过的大多数示例为此使用了Command-Pattern的变体。每个无法撤消的用户操作都会获得其自己的命令实例,该实例包含所有信息以执行该操作并将其回滚。然后,我们可以维护所有已执行命令的列表,然后可以将它们逐一回滚。

回答

刚刚在我的敏捷开发书中阅读了有关命令模式的信息,也许这很有潜力?

我们可以使每个命令都实现命令接口(具有Execute()方法)。如果要撤消,可以添加撤消方法。

更多信息在这里

回答

我们可能想参考Paint.NET代码进行的撤消操作,它们具有非常好的撤消系统。它可能比我们所需的要简单一些,但它可能会给我们一些想法和指导。

-亚当

回答

这可能是CSLA适用的情况。它旨在为Windows Forms应用程序中的对象提供复杂的撤消支持。

回答

为钉跳益智游戏编写求解器时,我必须这样做。我使每个动作都成为一个Command对象,该对象包含足够的信息,可以完成或者撤消该信息。就我而言,这就像存储起始位置和每次移动的方向一样简单。然后,我将所有这些对象存储在堆栈中,以便程序在回溯时可以轻松撤消所需的任意移动。

回答

如果我们正在谈论GoF,则Memento模式专门用于解决撤消问题。

回答

我曾经在一个应用程序上工作过,在该应用程序中,通过更新模型中维护的内部数据库中的字段,命令对应用程序模型(即CDocument ...我们正在使用MFC)所做的所有更改都保留在命令的末尾。因此,我们不必为每个操作编写单独的撤消/重做代码。每次更改记录时(在每个命令的末尾),撤消堆栈仅记住主键,字段名称和旧值。

回答

我支持Mendelt Siebenga,我们应该使用命令模式。我们使用的模式是Memento模式,随着时间的流逝,它会变得非常浪费。

由于我们正在处理内存密集型应用程序,因此我们应该能够指定允许撤消引擎占用多少内存,可以保存多少级撤消级别或者将它们保留到哪些存储中。如果不这样做,我们将很快面临由于机器内存不足而导致的错误。

我建议我们检查一下是否有一个框架已经使用我们选择的编程语言/框架为撤消模型创建了模型。发明新东西是很好的,但是最好在真实场景中采用已经编写,调试和测试过的东西。如果添加了我们正在编写的内容,这将有所帮助,以便人们可以推荐他们知道的框架。

回答

我读过的大多数示例都是通过使用命令或者memento模式来实现的。但是,我们也可以使用简单的双端队列结构在没有设计模式的情况下完成此操作。

回答

我们重复使用文件加载并保存对象的序列化代码,以方便的形式保存和恢复对象的整个状态。我们将那些序列化的对象连同有关执行了什么操作的一些信息一起推送到撤消堆栈上,如果没有足够的信息从序列化数据中收集信息,则将撤消该操作的提示。撤消和重做通常只是将一个对象替换为另一个对象(理论上)。

由于指向对象的指针(C ++)在执行一些奇怪的撤消重做序列(那些位置未更新为更安全的撤消识别标识符)时从未固定,因此存在许多错误。这个区域的错误通常...嗯...有趣。

对于速度/资源使用,某些操作可能是特殊情况,例如调整大小,四处移动。

多选也提供一些有趣的复杂性。幸运的是,我们在代码中已经有了分组概念。克里斯托弗·约翰逊(Kristopher Johnson)关于子项目的评论与我们所做的非常接近。

回答

我已经非常容易地成功地使用Memento模式实现了复杂的撤消系统,并且具有自然提供Redo框架的好处。一个更微妙的好处是,聚合操作也可以包含在单个"撤消"中。

简而言之,我们会有两叠纪念对象。一个用于撤消,另一个用于重做。每个操作都会创建一个新的备忘录,理想情况下将是一些调用以更改模型,文档(或者其他任何东西)的状态。这将添加到撤消堆栈中。当我们执行撤消操作时,除了对Memento对象执行撤消操作以再次更改模型外,我们还从撤消堆栈中弹出对象并将其直接推入重做堆栈。

如何实现更改文档状态的方法完全取决于实现。如果我们可以简单地进行API调用(例如ChangeColour(r,g,b)),则可以在其前面进行查询以获取并保存相应的状态。但是该模式还支持制作深层副本,内存快照,临时文件创建等,这完全取决于我们,因为它只是虚拟方法的实现。

要进行汇总操作(例如,用户Shift-选择要执行操作的对象的负载,例如删除,重命名,更改属性),代码将创建一个新的撤消堆栈作为单个备忘录,并将其传递给实际操作将各个操作添加到。因此,操作方法不需要(a)担心有全局堆栈,并且(b)可以以相同的方式编码,无论它们是独立执行还是作为一个聚合操作的一部分执行。

许多撤消系统仅在内存中,但是我想,如果我们愿意,可以将撤消堆栈持久化。

回答

正如其他人所述,命令模式是实现撤消/重做的一种非常强大的方法。但是我想提及命令模式有一个重要的优势。

使用命令模式实现撤消/重做时,可以通过抽象化(在某种程度上)对数据执行的操作并在撤消/重做系统中利用这些操作来避免大量重复的代码。例如,在文本编辑器中,剪切和粘贴是互补的命令(除了剪贴板的管理之外)。换句话说,剪切的撤消操作是粘贴,剪切的撤消操作被剪切。这适用于更简单的操作,例如键入和删除文本。

这里的关键是我们可以将撤消/重做系统用作编辑器的主要命令系统。无需编写"创建撤消对象,修改文档"之类的系统,而是可以"创建撤消对象,对撤消对象执行重做操作以修改文档"。

现在,诚然,许多人都在想:"好吧,这不是命令模式的重点吗?"是的,但是我看到太多的命令系统具有两组命令,一组用于立即操作,另一组用于撤消/重做。我并不是说不会有特定于立即操作和撤消/重做的命令,但是减少重复将使代码更易于维护。

回答

设计模式的第一部分(GoF,1994)有一个用例,用于将撤消/重做实现为设计模式。

回答

我认为在处理OP所暗示的规模和范围模型时,纪念品和命令都不实用。它们会起作用,但是维护和扩展将需要大量工作。

对于此类问题,我认为我们需要构建对数据模型的支持,以支持模型中涉及的每个对象的差异检查点。我已经做过一次,而且效果很好。我们要做的最大事情是避免在模型中直接使用指针或者引用。

每个对另一个对象的引用都使用一些标识符(例如整数)。每当需要该对象时,都可以从表中查找该对象的当前定义。该表包含每个对象的链接列表,每个对象都包含所有以前的版本,以及有关它们针对哪个检查点处于活动状态的信息。

撤消/重做的实现很简单:执行操作并建立一个新的检查点;将所有对象版本回滚到先前的检查点。

它在代码中需要一定的纪律,但是却具有许多优点:因为我们正在对模型状态进行差异存储,所以我们不需要深度副本。我们可以根据重做次数或者使用的内存来确定要使用的内存量(对于诸如CAD模型之类的东西非常重要);模型上运行的功能具有高度的可扩展性和低维护性,因为它们无需执行任何操作即可实现撤消/重做。

回答

作为参考,这是C#中撤消/重做的命令模式的简单实现:C#的简单撤消/重做系统。

回答

Codeplex项目:

这是一个简单的框架,可根据经典的Command设计模式向应用程序添加撤消/重做功能。它支持合并操作,嵌套事务,延迟执行(在顶级事务提交上执行)以及可能的非线性撤消历史记录(我们可以在其中选择多个要重做的操作)。

回答

我不知道这对我们是否有帮助,但是当我不得不在一个项目中做类似的事情时,我最终从http://www.undomadeeasy.com下载了UndoEngine一个很棒的引擎,并且我真的不太在乎它刚刚起作用的引擎盖下的东西。