我正在为民用机器应用编写结构建模工具.我有一个代表整个建筑的巨大模型类,其中包括节点,线元素,载荷等的集合,它们也是自定义类.
我已经编写了一个撤消引擎,它在每次修改模型后都会保存一份深层拷贝.现在我开始考虑是否可以进行不同的编码.我可以使用相应的反向修改器保存每个修改器动作的列表,而不是保存深层副本.这样我就可以将反向修改器应用于要撤消的当前模型,或者将修改器应用于重做.
我可以想象你将如何执行更改对象属性等的简单命令.但复杂命令如何?就像将新节点对象插入模型并添加一些保持对新节点的引用的线对象一样.
如何实现这一目标?
我见过的大多数例子都使用了Command-Pattern的变体.每个可撤消的用户操作都会获得自己的命令实例,其中包含执行操作并将其回滚的所有信息.然后,您可以维护已执行的所有命令的列表,并且可以逐个回滚它们.
当你处理OP意味着的大小和范围的模型时,我认为memento和命令都不实用.它们可以工作,但维护和扩展需要做很多工作.
对于这类问题,我认为您需要建立对数据模型的支持,以支持模型中涉及的每个对象的差异检查点.我曾经做过一次,它非常灵活.您要做的最重要的事情是避免在模型中直接使用指针或引用.
对另一个对象的每个引用都使用一些标识符(如整数).只要需要该对象,就可以从表中查找对象的当前定义.该表包含每个包含所有先前版本的对象的链接列表,以及有关它们处于活动状态的检查点的信息.
实现undo/redo很简单:执行操作并建立新的检查点; 将所有对象版本回滚到上一个检查点.
它在代码中需要一些规则,但有许多优点:因为你正在对模型状态进行差异存储,所以你不需要深层拷贝; 您可以使用任意数量的内存或内存来确定您想要使用的内存量(对CAD模型等非常重要); 对模型上运行的函数具有极高的可扩展性和低维护性,因为它们不需要执行任何操作来实现撤消/重做.
如果您正在谈论GoF,Memento模式专门解决撤销问题.
正如其他人所说,命令模式是实现撤销/重做的一种非常强大的方法.但是我想提到命令模式有一个重要的优点.
使用命令模式实现撤消/重做时,可以通过抽象(在一定程度上)对数据执行的操作并在撤消/重做系统中使用这些操作来避免大量重复代码.例如,在文本编辑器中,剪切和粘贴是互补命令(除了剪贴板的管理).换句话说,剪切的撤消操作是粘贴,并且剪切粘贴的撤消操作.这适用于键入和删除文本等更简单的操作.
这里的关键是您可以使用撤消/重做系统作为编辑器的主要命令系统.而不是编写诸如"创建撤消对象,修改文档"之类的系统,您可以"创建撤消对象,对撤消对象执行重做操作以修改文档".
现在,诚然,很多人都在想自己"嗯,这不是命令模式的一部分吗?" 是的,但是我看到太多的命令系统有两组命令,一组用于立即操作,另一组用于撤销/重做.我并不是说没有特定于立即操作和撤消/重做的命令,但减少重复将使代码更易于维护.
您可能希望引用Paint.NET代码进行撤消 - 它们有一个非常好的撤消系统.它可能比您需要的更简单,但它可能会给您一些想法和指导.
-亚当
这可能是CSLA适用的情况.它旨在为Windows窗体应用程序中的对象提供复杂的撤消支持.
我已经使用Memento模式成功实现了复杂的撤销系统 - 非常简单,并且具有自然提供Redo框架的好处.更微妙的好处是聚合操作也可以包含在单个撤消中.
简而言之,您有两堆纪念品.一个用于撤销,另一个用于重做.每个操作都会创建一个新的纪念品,理想情况下会有一些调用来改变模型,文档(或其他)的状态.这会添加到撤消堆栈中.执行撤消操作时,除了在Memento对象上执行撤消操作以再次更改模型之外,还可以从撤消堆栈中弹出对象并将其直接拖到重做堆栈上.
如何实现更改文档状态的方法完全取决于您的实现.如果您可以简单地进行API调用(例如,ChangeColour(r,g,b)),则在其前面加上查询以获取并保存相应的状态.但该模式还将支持制作深拷贝,内存快照,临时文件创建等 - 这完全取决于您,因为它只是一个虚拟方法实现.
要进行聚合操作(例如,用户Shift-选择一组对象来执行操作,例如删除,重命名,更改属性),您的代码会创建一个新的Undo堆栈作为单个纪念品,并将其传递给实际操作添加单个操作.因此,您的操作方法不需要(a)要担心全局堆栈,并且(b)无论是单独执行还是作为一个聚合操作的一部分执行,都可以编码相同.
许多撤消系统只在内存中,但如果你愿意,你可以保留撤销堆栈,我想.
刚刚阅读了我的敏捷开发书中的命令模式 - 也许这有潜力?
您可以让每个命令都实现命令接口(具有Execute()方法).如果要撤消,可以添加撤消方法.
更多信息在这里