2011-04-17 76 views
0

我有一个C++应用程序,根据经典Model-View-Controller pattern设计。该模型通过控制器接口由外部源通过Command pattern进行修改。这些命令由一个Action对象(及其派生物)表示。在MVC设计中撤销功能

现在我想能够撤消修改,但我的问题是我没有在我的控制器中的getters,只有setter。这似乎很合乎逻辑,因为没有理由有人应该能够通过控制器获得关于模型的信息。因此,我无法让Action对象存储模型的状态,因为他们无法访问它。

如何解决这个问题?我想保留我的应用程序尽可能扩展,我不太确定哪个选项最适合。我到目前为止的方法是:

  1. 将getter方法放在控制器中。这似乎违背了MVC模式。
  2. 给动作一个指向视图的指针。然后,该操作可以:
    1. 使用单个获取器来获取要修改的模型的特定元素的状态。
    2. 使用由查看器实现的Memento方法。

也许还有更好的方法来做到这一点?现在,成为最好的选择似乎是2,子选项1(子选项2,我可能存储更多的状态比撤消一个动作所需的更多)。

注:我知道还有关于如何实现撤消操作的其他问题。然而,我发现的唯一答案给了使用Command或Memento模式的建议。我知道这是最有可能的路要走。我所要求的是如何在MVC设计中尽可能地将其扩展为可扩展的。

[编辑]我不喜欢的纪念图案是它迫使我存储一个完整的状态。假设我的模型是一个1000x1000矩阵,我的命令是ChangeOneValueAtLocation。为了能够撤销它的更改,ChangeOneValueAtLocation对象只需要存储它正在改变的位置的先前值,但对于Memento来说这似乎不可能。我的模型越大,这个问题就会变得最大。

[编辑2]另一个问题我在本申请的特定情况中具有纪念品:每一个命令对象可以在模型执行方法,有,做完全相反(或可以很容易地被诱导的方法这样做)。这就是为什么我会觉得必须存储整个状态是一件很浪费的事情,应该不需要这样做,恢复单个命令非常简单,唯一的问题是让数据能够做到这一点。

此外,我不需要能够撤消一个特定的命令,只有在我的历史堆栈上的最顶层。

回答

2

我也支持撤消工作包含撤消支持的模型图层。模型方面有很多方法可以处理这个问题。第一个也是最明显的是模型自己用“标签”记住变化的历史,但是这可能很难为所有模型类同步。

另一个选择是创建一个具有“事务”概念的历史记录管理器,该历史记录管理器可以生成一个撤消点并拍摄模型的快照,或者开始记录更改(以减少内存使用量) ,或者记录导致模型变化的命令等等。模型通知管理者变化,最后你完成交易(或者不交,因为下一次交易的开始可以是前一交易的结束)。一旦你添加了回滚到某个点的能力,工作就完成了。通过在这个管理器类中使事情稍微复杂一点,您可以创建一个撤消树(就像emacs中的那个),所以它也是一种相当灵活的方法。

虽然上面的解决方案并不在模型层中。它是由模型和控制器驱动的支持类。如果删除事务概念,那么它完全是模型驱动的,但实现撤销操作的概念可能有点棘手。如果将其更改为命令代理,则它是控制器使用的唯一实体,显然是一种模型。在这一点上,选择一种方法比另一种方法设计太粗糙,但我倾向于“交易”模式。实施起来感觉很简单。

+0

我决定采用这种方法,因为它可以让我轻松地将模型及其相关历史记录传输到异地客户端。将历史记录放入控制器会使这变得更加困难。此外,我的模型只有一个前端类,所以我不认为非原子撤销问题“sehe”提及会成为问题。 – Darhuuk 2011-04-20 12:11:21

0

在模型本身中构建撤消功能。让你模型保存一个命令列表。视图向模型传递撤消信号时,按相反顺序运行命令。

2

我真的建议建立撤销树到控制器中

构建成的模型可以运行你陷入困境:

  • 的“模式”通常是按次分段(每个视图有它自己的局部模型)
  • 这将导致非原子撤消(撤消的操作部分由于视图不知道什么其他的事情(型号)将必须被撤销等)

控制器是“行动调度员”,所以不得不说

  1. 克隆状态(所有型号)快照
  2. 添加操作参考历史快照
  3. 运行动作

然后撤消将

  1. 弹出行动关闭histor Ÿ堆栈(可选推到“未来”栈)
  2. 恢复快照
  3. 显示视图

此外,还要有高水平的动作(见组合模式或命令模式)

+0

但现在的问题仍然是:我该如何干净地实现它?我的Command类如何获得它的快照?从实现一个Memento模式的View中,需要一个指向View的指针?从控制器中的一个获取器开始运行,从而打破了MVC模式? – Darhuuk 2011-04-17 21:01:22

+0

控制器_is_负责状态。所以,让控制器记住状态,不会打破MVC模式。但是,不应该将其暴露出来。你可以做Controller.Rollback() - 控制器可以选择将实际实现委托给Model(如果它实现了Memento,例如) – sehe 2011-04-17 21:04:08

+1

事情是我想让状态进入我的Command对象。因此''Controller.rollback()''可以从堆栈中弹出最后一个命令并调用''undo()''。我想我可以让Controller在调用''execute()''之前将状态放入Command中。我不喜欢保存完整状态的想法(请参阅我原始问题中的编辑以获取更多信息)。 – Darhuuk 2011-04-17 21:37:28