4

我已经对如何实现这个问题的标题进行了一些研究。我正在开发的应用程序已经开发了几年左右(虽然进展缓慢,你们都知道它在现实世界中的情况)。现在我需要加入 Undo/Redo 多级功能。现在说“你应该在开始之前考虑过这个”有点晚了......好吧,我们确实考虑过 - 我们对此没有采取任何行动,现在它就在这里。通过搜索 SO(和外部链接),我可以看到两种最常见的方法似乎是......

命令模式

纪念品图案

命令模式看起来会做很多工作,我只能想象它也会在这个过程中抛出数千个错误,所以我不太喜欢那个。

Memento 模式实际上很像我对此的想法。我在想如果有什么方法可以快速拍摄当前内存中的对象模型的快照,那么我就可以将它存储在某个地方(也许也在内存中,也许在文件中)。这似乎是一个好主意,我能看到的唯一问题是它如何与我们已经编写的内容集成。您会看到我们的应用程序,它在一个大面板(可能有数百个)中绘制图像,然后允许用户通过 UI 或通过自定义构建的属性网格来操作它们。整个应用程序与一个大的观察者模式相连。第二个任何变化,事件被触发,所有需要更新的东西都会发生。这很好,但我不禁想到,如果用户在属性网格上的 texfield 中输入文本,在 UI 赶上之前会有一点延迟(似乎每次用户按下一个键,都会添加一个新快照到撤消列表)。所以我对你的问题是......

  • 您是否知道可能有效的 Memento 模式的任何好的替代方案。
  • 您认为 Memento 模式是否适合这里,或者它会使应用程序变慢太多。
  • 如果备忘录模式是要走的路,那么制作对象模型快照的最有效方法是什么(我正在考虑将其序列化或其他方式)
  • 快照应该存储在内存中还是可以将它们放入文件中?

如果你已经走到这一步,谢谢你的阅读。您的任何输入都将是有价值的,非常感谢。

4

4 回答 4

5

好吧,这是我对这个问题的想法。

1-您需要多级撤消/重做功能。所以您需要存储用户执行的操作,这些操作可以存储在堆栈中。

2-您的第二个问题如何识别我认为通过 Memento 模式进行的操作已更改的内容,这是一个相当大的挑战。Memento 就是在你的记忆中撕裂初始对象状态。

或者,您需要存储操作更改的内容,以便您可以使用此信息撤消操作。

命令模式是为撤消/重做功能而设计的,我会说它已经晚了,但它值得实现已经使用了几年并适用于大多数应用程序的设计。

于 2010-12-02T17:59:14.367 回答
3

如果性能允许,您可以在每次操作之前序列化您的域。如果物体本身并不大,那么几百个物体并不多。

由于您的对象图可能很重要(即使用继承、循环等),因此集成的 XmlSerializer 和 JsonSerializer 是毫无疑问的。Json.net 支持这些,但对某些类型(本地日期时间、数字等)进行了一些有损转换,所以它也很糟糕。

我认为 protobuf 序列化程序需要某种形式的 DTD(.proto 文件)或使用将其名称映射到数字的属性来装饰所有属性,因此它可能不是最佳的。

BinaryFormatter 可以序列化大多数东西,你只需要用 [Serializable] 属性装饰所有类。但是我自己没有使用它,所以可能存在我不知道的陷阱。也许与单身人士或事件有关。

于 2010-12-02T18:55:35.703 回答
1

撤消/重做的关键是

  • 知道你需要保存和恢复什么状态
  • 知道何时需要保存状态

事后添加撤消/重做总是一件痛苦的事情 - (我知道这个评论现在对你没有用,但最好在开始之前将支持设计到应用程序框架中,因为它可以帮助人们使用撤消 -整个开发过程中的友好模式)。

可能最简单的方法是基于纪念品的方法:

  • 找到构成“文档”的所有数据。你能以某种方式统一这些数据,使其形成一个连贯的整体吗?通常,如果您可以将文档结构序列化为文件,那么您需要的逻辑在序列化系统中,这样就可以进入。直接使用它的缺点通常是您通常必须序列化所有内容,以便撤消将是巨大而缓慢的。如果可能,重构代码,以便 (a) 在整个应用程序中使用一个通用的序列化接口(因此可以使用通用调用保存/恢复数据的任何部分),以及 (b) 每个子系统都被封装因此对数据的修改必须通过一个通用接口(而不是很多人直接修改成员变量,它们都应该调用对象提供的 API 来请求它对自身进行更改)和 (c) 数据的每个子部分都保留一个“版本号”。每次进行更改时(通过 (b) 中的界面),它应该增加该版本号。这种方法意味着您现在可以扫描整个文档并使用版本号来查找自上次查看以来已更改的部分,然后序列化最小数量以保存和恢复更改的状态。

    • 提供一种可以记录单个撤消步骤的机制。这意味着允许多个系统对数据结构进行更改,然后当所有内容都更新后,触发撤消记录。确定何时执行此操作可能很棘手,但通常可以通过在您的 UI 完成处理每个输入事件时扫描您的文档以查找消息循环中的更改(见上文)来完成。

除此之外,我建议采用基于命令的方法,因为除了撤消/重做之外,它还有很多好处。

于 2010-12-02T19:48:57.490 回答
1

您可能会发现 Monitored Undo Framework 很有用。http://muf.codeplex.com/

它使用类似于 memento 模式的东西,通过监视发生的更改并允许您将委托放在撤消堆栈上,这将撤消/重做更改。

我考虑了一种可以序列化/反序列化文档的方法,但担心开销。相反,我按属性基础监视属性上模型(或视图模型)的变化。然后,根据需要,我使用 MUF 库来“批量”相关更改,以便将它们撤消/重做作为更改单元。

你有你的 UI 设置来响应底层模型的变化这一事实很好。听起来您可以在那里注入撤消/重做逻辑,并且更改会冒泡到 UI。

我认为您不会看到太多滞后或性能下降。我有一个类似的应用程序,我们根据模型中的数据绘制了一个图表。到目前为止,我们在这方面取得了不错的成绩。

您可以在http://muf.codeplex.com/上的 codeplex 站点上找到更多信息和文档。该库也可通过 NuGet 获得,支持 .NET 3.5、4.0、SL4 和 WP7。

于 2011-06-21T03:24:00.870 回答