【问题标题】:Undo functionality in an MVC designMVC 设计中的撤消功能
【发布时间】:2011-08-07 09:58:21
【问题描述】:

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

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

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

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

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

注意:我知道关于如何实现撤消操作还有其他问题。但是,我找到的唯一答案给出了使用命令或备忘录模式的建议。我知道这很可能是要走的路。我要问的是如何在 MVC 设计中尽可能干净和可扩展地集成它。

[编辑] 我不喜欢 Memento 模式的地方在于它迫使我存储完整的状态。假设我的模型是 1000x1000 矩阵,我的命令是 ChangeOneValueAtLocation。为了能够撤消其更改,ChangeOneValueAtLocation 对象只需要存储它正在更改的位置的先前值,但对于 Memento,这似乎是不可能的。我的模型越大,这个问题就越大。

[Edit 2] Memento 在这个应用程序的特定情况下遇到的另一个问题:对于一个 Command 对象可以在模型上执行的每个方法,都有一个方法完全相反(或者可以很容易被哄着这样做)。这就是为什么我觉得必须存储整个状态是一种浪费,应该没有必要,恢复单个命令非常简单,唯一的问题是获取数据才能做到这一点。

另外,我不需要能够撤消特定命令,只需撤消我历史堆栈中最顶部的命令。

【问题讨论】:

    标签: c++ design-patterns


    【解决方案1】:

    在模型本身中构建撤消功能。让你模型保留一个命令列表。当您的视图将撤消信号传递给模型时,以相反的顺序运行命令。

    【讨论】:

      【解决方案2】:

      我真的建议将撤消树构建到您的控制器中

      将其构建到模型中可能会给您带来麻烦:

      • “模型”通常按视图分段(每个视图都有自己的部分模型)
      • 这将导致非原子撤消(撤消操作的部分,因为视图不知道必须撤消哪些其他事情(模型)等)

      控制器是“动作调度器”,所以它不得不说

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

      然后撤消将是

      1. 从历史堆栈中弹出操作(可选择推送到“未来”堆栈)
      2. 恢复快照
      3. 显示视图

      此外,使撤消与高级操作一起使用(请参阅复合模式或命令模式)

      【讨论】:

      • 但现在的问题仍然是:如何干净利落地实现这一点?我的 Command 类如何获得它的快照?从一个实现备忘录模式的视图,因此需要一个指向视图的指针?从它正在运行的 Controller 中的 getter,从而打破 MVC 模式?
      • 控制器对状态负责。因此,让控制器记住状态,并没有破坏 MVC 模式。但是,应该没有需要公开它。您可以执行 Controller.Rollback() - 控制器可以选择将实际实现委托给模型(如果它确实实现了 Memento,例如)
      • 问题是我想将状态放入我的 Command 对象中。所以Controller.rollback() 可以将最后一个命令从堆栈中弹出并调用undo()。我想我可以让控制器在调用execute() 之前将状态放入命令中。不过,我不喜欢保存完整状态的想法(有关更多信息,请参阅我原来的问题中的编辑)。
      • Darhuuk:对我来说听起来是个好概念,只要命令不被打乱。如果您不保存完整状态,则将更难验证回滚的正确性。余额由您决定
      【解决方案3】:

      我还支持包含撤消支持的模型层。在模型方面有很多方法可以处理这个问题。第一个也是最明显的是模型本身通过“标签”记住更改的历史,但这可能很难为所有模型类同步。

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

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

      【讨论】:

      • 我决定采用这种方法,因为它可以让我轻松地将模型及其相关历史传输到非现场客户。将历史记录放在控制器中会使这变得更加困难。此外,我的模型只有一个前端类,所以我认为sehe 提到的非原子撤消问题不会成为问题。
      猜你喜欢
      • 2011-03-27
      • 2014-03-18
      • 1970-01-01
      • 1970-01-01
      • 2013-10-03
      • 2021-08-05
      • 2018-04-23
      • 1970-01-01
      • 2016-02-28
      相关资源
      最近更新 更多