本文是从英文的网站翻译,用作自己的整理和记录,有修改调整。水平有限,欢迎指正。原文地址:地址

一、引言

这是关于用C#编写多级撤销和重做实现的系列文章的第二部分。本系列展示了针对同一问题的三种方法的撤销/重做实现,以及我们如何使用这些方法实现不同场景的撤销/恢复。这些方法使用单对象状态变化、命令模式和备忘录模式。

  • 单对象状态变化(不推荐)
  • 命令模式(推荐)
  • 备忘录模式(推荐)

正如我们所知道的,撤销/重做没有通用的解决方案,撤销/重做的实现对于每个应用程序都具有定制化部分。因此,本系列文章的每个部分首先讨论了该模式下的设计思路,然后在实例程序中展示了如何实现。该系列,撤销/重做操作使用三种不同的设计方式,因此您可以将每个模式实现与其他方法实现进行比较,并选择最适合您需求的方法。在每个部分文章中我们还讨论了每种方法的优缺点。

二、Undo/Redo实现的基本思路

我们知道,应用程序在每次操作后都会改变其状态。当应用程序操作运行时,它会改变其状态。因此,如果我们想Undo撤销,我们必须回到以前的状态。因此,为了能够恢复到以前的状态,我们需要存储应用程序运行时的状态。为了支持Redo重做,我们必须从当前状态转到下一个状态。

要实现Undo/Redo,我们必须存储应用程序的状态,并且必须转到上一状态进行撤销,转至下一状态进行重做。因此,我们必须维护应用程序的状态以支持Undo/Redo。为了在三种设计方法中维护应用程序的状态,我们使用了两个堆栈。一个堆栈包含撤消操作的状态,第二个堆栈包含重做操作的状态。撤消操作弹出撤消堆栈以获取以前的状态,并将以前的状态设置为应用程序。同样,重做操作弹出重做堆栈以获得下一个状态,并为应用程序设置下一状态。

现在我们知道,实现撤销-重做操作就是在应用程序的每个操作之后保持状态。现在的问题是,这种方法如何保持状态?在命令模式中,我们将单个操作的更改保留在ICommand对象中,该对象用于作为状态的特定操作类型。

大家可以有兴趣的可以去看看设计模式中的命令模式

三、命令模式通用思路

在以下步骤中讨论了如何使用命令模式设计思路:

3.1 第一步

首先确定要支持 Undo/Redo 的操作。然后,确定在哪个容器中支持撤消/重做,以及要支持撤消/恢复的对象。

3.2 第二步

为每个标识的操作创建从ICommand继承的command类。每个command 类将包含一个需要支持 Undo/Redo 属性。ICommand接口如下所示:

interface ICommand
{void Execute();void UnExecute();
}

在Execute()方法中,您将使用命令的属性执行操作。在Unexecuted()方法中,您将使用命令的属性执行撤消操作。在这里,命令的属性包含相应命令进行撤消/重做操作所需的更改,以及更改对象的引用。

特别注意:如果同一操作在不同对象上的行为不同,则必须为此操作发出多个命令。这是每个对象类型的操作命令。

3.3 第三步

然后创建一个名为UndoRedo的类,该类包含两个堆栈。第一个用于撤消操作,第二个用于重做操作。此类实现了Undo方法、Redo方法和许多InsertInUnDoRedo方法,以在Undo/Redo系统中插入ICommand对象。调用InsertInUnDoRedo方法时,将ICommand对象插入到撤消堆栈中,以使操作撤消/重做启用并清除重做堆栈。

3.5.1 在每次撤消操作中:

  • 首先,您将检查撤消Undo堆栈是否为空。如果为空,则返回,否则继续。
  • 如果不是,则pop 从UndoStack 中弹出 ICommand
  • 然后把这个 command  push 到 RedoStack 
  • 然后,调用ICommand类中的Unexecute方法

3.5.2在每个重做操作中:

  • 首先,您将检查RedoStack堆栈是否为空。如果为空,则返回,否则继续。
  • 如果不是,则从RedoStack 弹出Icommand
  • Icommand push到Undostack
  • 然后,调用Icommand类中的execute方法

3.4 第四步

当您在应用程序中执行不同的操作时,创建这个操作类型的Command类,并将其push撤消/重做系统。当您需要执行undo 撤消操作时,只需从应用程序中调用UndoRedo类的undo方法,当您需要进行重做操作时,则只需从您的应用程序调用redo 重做操作。

四、程序实现

下面讨论了使用命令模式示例应用程序的撤消/重做实现:

5.1 第一步

示例应用程序中有四个操作,分别是移动、调整大小、插入和删除。对象是矩形、椭圆,容器是WPF画布。

5.2 第二步

现在我们将为继承ICommand接口的四个操作中的每一个创建四个命令类。

5.2.1 MoveCommand

class MoveCommand : ICommand{private Thickness _ChangeOfMargin;private FrameworkElement _UiElement;public MoveCommand(Thickness margin, FrameworkElement uiElement){_ChangeOfMargin = margin;_UiElement = uiElement;}#region ICommand Memberspublic void Execute(){_UiElement.Margin = new Thickness(_UiElement.Margin.Left +_ChangeOfMargin.Left, _UiElement.Margin.Top+ _ChangeOfMargin.Top, _UiElement.Margin.Right +_ChangeOfMargin.Right, _UiElement.Margin.Bottom +_ChangeOfMargin.Bottom);}public void UnExecute(){_UiElement.Margin = new Thickness(_UiElement.Margin.Left -_ChangeOfMargin.Left, _UiElement.Margin.Top -_ChangeOfMargin.Top, _UiElement.Margin.Right -_ChangeOfMargin.Right, _UiElement.Margin.Bottom -_ChangeOfMargin.Bottom);}#endregion}

由于移动操作仅更改几何对象的边距,因此移动命令将包含边距的更改,即几何对象参考。在move命令中,Execute方法通过添加margin更改来完成对_UiElement几何对象的更改,而unexecutes方法通过减去已应用的更改来撤消操作。为此,它从几何对象UIelement中减去边缘变化。

5.2.2 ResizeCommand

class ResizeCommand : ICommand{private Thickness _ChangeOfMargin;private double _ChangeofWidth;private double _Changeofheight;private FrameworkElement _UiElement;public ResizeCommand(Thickness margin, double width,double height, FrameworkElement uiElement){_ChangeOfMargin = margin;_ChangeofWidth = width;_Changeofheight = height;_UiElement = uiElement;}#region ICommand Memberspublic void Execute(){_UiElement.Height = _UiElement.Height + _Changeofheight;_UiElement.Width = _UiElement.Width + _ChangeofWidth;_UiElement.Margin = new Thickness(_UiElement.Margin.Left + _ChangeOfMargin.Left,_UiElement.Margin.Top+ _ChangeOfMargin.Top, _UiElement.Margin.Right +_ChangeOfMargin.Right, _UiElement.Margin.Bottom +_ChangeOfMargin.Bottom);}public void UnExecute(){_UiElement.Height = _UiElement.Height - _Changeofheight;_UiElement.Width = _UiElement.Width - _ChangeofWidth;_UiElement.Margin = new Thickness(_UiElement.Margin.Left -_ChangeOfMargin.Left, _UiElement.Margin.Top -_ChangeOfMargin.Top, _UiElement.Margin.Right -_ChangeOfMargin.Right, _UiElement.Margin.Bottom -_ChangeOfMargin.Bottom);}#endregion}

“调整大小”操作更改几何对象的边距、高度和宽度,因此“调整大小”命令可保存边距的更改、高度的更改、宽度的更改以及几何对象的引用。在resize命令中,Execute方法通过添加边距变化、高度变化和宽度变化来完成对_UiElement几何对象的更改。未执行方法通过减去已应用的更改来撤消操作。为此,它从几何对象_UIelement中减去边距变化、高度变化和宽度变化。

5.2.3 InsertCommand

class InsertCommand : ICommand{private FrameworkElement _UiElement;private Canvas _Container;public InsertCommand(FrameworkElement uiElement, Canvas container){_UiElement = uiElement;_Container = container;}#region ICommand Memberspublic void Execute(){if (!_Container.Children.Contains(_UiElement)){_Container.Children.Add(_UiElement);}}public void UnExecute(){_Container.Children.Remove(_UiElement);}#endregion}

插入操作是在面板中插入几何对象,插入命令保存几何对象和画布的引用。在insert命令中,Execute方法将几何对象添加到画布,Unexecute方法从画布中删除几何对象。

5.2.4 DeleteCommand

class DeleteCommand : ICommand{private FrameworkElement _UiElement;private Canvas _Container;public DeleteCommand(FrameworkElement uiElement, Canvas container){_UiElement = uiElement;_Container = container;}#region ICommand Memberspublic void Execute(){_Container.Children.Remove(_UiElement);}public void UnExecute(){_Container.Children.Add(_UiElement);}#endregion} 

当删除操作从面板中删除几何对象时,delete命令保存几何对象和画布的引用。在delete命令中,Execute方法从画布中删除几何对象,而Unexecute方法将几何对象添加到画布中。

5.3 第三步

现在我们将根据通用模型的描述实现UndoRedo类。

public class UnDoRedo{private Stack<ICommand> _Undocommands = new Stack<ICommand>();private Stack<ICommand> _Redocommands = new Stack<ICommand>();private Canvas _Container;public Canvas Container{get { return _Container; }set { _Container = value; }}public void Redo(int levels){for (int i = 1; i <= levels; i++){if (_Redocommands.Count != 0){ICommand command = _Redocommands.Pop();command.Execute();_Undocommands.Push(command);}}}public void Undo(int levels){for (int i = 1; i <= levels; i++){if (_Undocommands.Count != 0){ICommand command = _Undocommands.Pop();command.UnExecute();_Redocommands.Push(command);}}}#region UndoHelperFunctionspublic void InsertInUnDoRedoForInsert(FrameworkElement ApbOrDevice){ICommand cmd = new InsertCommand(ApbOrDevice, Container);_Undocommands.Push(cmd);_Redocommands.Clear();}public void InsertInUnDoRedoForDelete(FrameworkElement ApbOrDevice){ICommand cmd = new DeleteCommand(ApbOrDevice, Container);_Undocommands.Push(cmd);_Redocommands.Clear();}public void InsertInUnDoRedoForMove(Point margin, FrameworkElement UIelement){ICommand cmd = new MoveCommand(new Thickness(margin.X, margin.Y, 0, 0), UIelement);_Undocommands.Push(cmd);_Redocommands.Clear();}public void InsertInUnDoRedoForResize(Point margin, double width, double height, FrameworkElement UIelement){ICommand cmd = new ResizeCommand(new Thickness(margin.X, margin.Y, 0, 0), width, height, UIelement);_Undocommands.Push(cmd);_Redocommands.Clear();}#endregion}

第一堆栈_Undocommands持有可撤销操作,第二堆栈 _Redo commands持有redoable 操作。当InsertInUnDoRedo 方法被调用时,Icommand对象被插入到_Undocommands栈中,以使命令成为可撤销操作,并清除重做堆栈。此处级别决定您要执行撤消的次数。

5.4 第四步

在你的应用程序中执行不同的操作时,给这个操作定义一个Command object,使用InsertInUnDoRedo方法新增到Undo/Redo系统。当从UI单击Undo时,我们调用UndoRedo类的Undo方法,当从UI中单击Redo时,我们将调用UndoRedo类的Redo方法。

在这里,没有明确设置撤消堆栈和重做堆栈的大小,保存的撤消重做状态的数量取决于系统内存。

六、优势和缺点

6.1 优点

其可维护性良好,不包含任何冗余信息。它不是内存密集型(对比第三种方法)

6.2 缺点

命令模式的唯一缺点是,无论操作大小,都必须使命令类型等于操作数。随着操作的增加,命令也会增加

撤销和重做实现-第二部分(命令模式)相关推荐

  1. 图解Java设计模式学习笔记——行为型模式(模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式)

    一.模板方法模式(模板模式)--钩子方法 1.需求-豆浆制作问题 编写制作豆浆的程序,说明如下: 制作豆浆的流程选材-->添加配料-->浸泡-->放到豆浆机打碎. 通过添加不同的配料 ...

  2. 在Unity实现游戏命令模式

    本文由开发者Najmm Shora介绍在Unity中通过使用命令模式实现回放功能,撤销功能和重做功能.我们可以使用该方法来强化自己的策略类游戏. 你是否想知道<超级食肉男孩>(Super ...

  3. 举例说,在命令模式(Command Pattern)

    在前面加上 谈到命令,大部分的人脑海中会想到以下这幅画面   这在现实生活中是一副讽刺漫画,做决定的人不清楚运行决定的人有何特点,瞎指挥.外行领导内行说的就是这样的.只是在软件设计领域,我们显然要为这 ...

  4. 23Command(命令)模式

    技术交流QQ群:1027579432,欢迎你的加入! 1.行为变化模式 在组件的构建过程中,组件行为的变化经常会导致组件本身剧烈的变化.行为变化模式将组件的行为和组件本身进行解耦,从而支持组件行为的变 ...

  5. 设计模式- 命令模式

    Gof定义 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 理解 对命令模式我有一些肤浅的认识,也许是不对的.但我还是写一下.其实在 ...

  6. 设计模式心得:三——命令模式

    继续设计模式心得体验--命令模式. 命令模式: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 一般在命令模式中有三个元素,invo ...

  7. (Command Pattern)命令模式

    定义 将"请求"封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 结构图: 命令模式的角色划分: Reciever(命令的接收者):接收命 ...

  8. C++设计模式(8)——命令模式

    命令模式 亦称:动作.事务.Action.Transaction.Command 意图 命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象. 该转换让你能根据不同的请 ...

  9. 【源码分析设计模式 13】命令模式

    一.基本介绍 1.在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作时哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计 ...

最新文章

  1. hdu4515 小模拟
  2. Android应用程序键盘(Keyboard)消息处理机制分析(14)
  3. 浅谈 CTR 预估模型发展史
  4. 知乎上高赞的40个有趣回复,很精辟!
  5. iOS中都有什么设计模式?各个设计模式的作用 (转载)
  6. Python 获取重定向url
  7. 前端根据设计图精确开发 (攻具)
  8. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_02 递归_1_递归概念分类注意事项...
  9. 量化投资知识,量化交易
  10. 常用来进行钢结构节点输出的软件是什么_钢结构、Tekla及建筑工业化厂商对Tekla软件的开发和应用...
  11. 什么是集合竞价和连续竞价
  12. 微信小程序:常用功能6——点击图片,实现图片的预览功能wx.previewImage(Object object, boolean showmenu)
  13. 华清远见嵌入式培训_第六周回顾与反思
  14. 原来棒棒糖还有这功能~
  15. SAP UI5 应用开发教程之七十九 - 采用测试驱动开发理念(Test Driven Development)进行 SAP UI5 应用的功能开发(一)的试读版
  16. lisp的vla函数画矩形_AutoLISP反应器vlr-object-reactor函数应用
  17. 【登录时验证码一直错误】阿里云SLB负载均衡,如何session共享
  18. vuecli2升级至vuecli3
  19. 解决Ubuntu22.04中向日葵无法被控制问题
  20. A 股指数历史数据 API 数据接口

热门文章

  1. 十分钟教会你们如何架设传奇
  2. 2017年09月19日泰国清迈曼谷普吉岛三地游
  3. USART_GetITStatus(USART3,USART_IT_IDLE) ==RESET进入中断问题
  4. python编程的就业方向_2021年python编程就业方向是怎样的?
  5. android 平板 办公,技德Remix超级平板如何让安卓系统成为办公神器
  6. navicat连接linux线上MySQL出现2003错误码
  7. ChatGpt 能取代人类吗?
  8. 由提交storm项目jar包引发对jar的原理的探索
  9. 马化腾徐少春等中国富豪获住房补贴 政策真空网民质疑
  10. 【iPhone/iPad】苹果iOS9正式版更新升级及固件刷机教程