撤销和重做实现-第二部分(命令模式)
本文是从英文的网站翻译,用作自己的整理和记录,有修改调整。水平有限,欢迎指正。原文地址:地址
一、引言
这是关于用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 缺点
命令模式的唯一缺点是,无论操作大小,都必须使命令类型等于操作数。随着操作的增加,命令也会增加。
撤销和重做实现-第二部分(命令模式)相关推荐
- 图解Java设计模式学习笔记——行为型模式(模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式)
一.模板方法模式(模板模式)--钩子方法 1.需求-豆浆制作问题 编写制作豆浆的程序,说明如下: 制作豆浆的流程选材-->添加配料-->浸泡-->放到豆浆机打碎. 通过添加不同的配料 ...
- 在Unity实现游戏命令模式
本文由开发者Najmm Shora介绍在Unity中通过使用命令模式实现回放功能,撤销功能和重做功能.我们可以使用该方法来强化自己的策略类游戏. 你是否想知道<超级食肉男孩>(Super ...
- 举例说,在命令模式(Command Pattern)
在前面加上 谈到命令,大部分的人脑海中会想到以下这幅画面 这在现实生活中是一副讽刺漫画,做决定的人不清楚运行决定的人有何特点,瞎指挥.外行领导内行说的就是这样的.只是在软件设计领域,我们显然要为这 ...
- 23Command(命令)模式
技术交流QQ群:1027579432,欢迎你的加入! 1.行为变化模式 在组件的构建过程中,组件行为的变化经常会导致组件本身剧烈的变化.行为变化模式将组件的行为和组件本身进行解耦,从而支持组件行为的变 ...
- 设计模式- 命令模式
Gof定义 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 理解 对命令模式我有一些肤浅的认识,也许是不对的.但我还是写一下.其实在 ...
- 设计模式心得:三——命令模式
继续设计模式心得体验--命令模式. 命令模式: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 一般在命令模式中有三个元素,invo ...
- (Command Pattern)命令模式
定义 将"请求"封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 结构图: 命令模式的角色划分: Reciever(命令的接收者):接收命 ...
- C++设计模式(8)——命令模式
命令模式 亦称:动作.事务.Action.Transaction.Command 意图 命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象. 该转换让你能根据不同的请 ...
- 【源码分析设计模式 13】命令模式
一.基本介绍 1.在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作时哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计 ...
最新文章
- hdu4515 小模拟
- Android应用程序键盘(Keyboard)消息处理机制分析(14)
- 浅谈 CTR 预估模型发展史
- 知乎上高赞的40个有趣回复,很精辟!
- iOS中都有什么设计模式?各个设计模式的作用 (转载)
- Python 获取重定向url
- 前端根据设计图精确开发 (攻具)
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_02 递归_1_递归概念分类注意事项...
- 量化投资知识,量化交易
- 常用来进行钢结构节点输出的软件是什么_钢结构、Tekla及建筑工业化厂商对Tekla软件的开发和应用...
- 什么是集合竞价和连续竞价
- 微信小程序:常用功能6——点击图片,实现图片的预览功能wx.previewImage(Object object, boolean showmenu)
- 华清远见嵌入式培训_第六周回顾与反思
- 原来棒棒糖还有这功能~
- SAP UI5 应用开发教程之七十九 - 采用测试驱动开发理念(Test Driven Development)进行 SAP UI5 应用的功能开发(一)的试读版
- lisp的vla函数画矩形_AutoLISP反应器vlr-object-reactor函数应用
- 【登录时验证码一直错误】阿里云SLB负载均衡,如何session共享
- vuecli2升级至vuecli3
- 解决Ubuntu22.04中向日葵无法被控制问题
- A 股指数历史数据 API 数据接口
热门文章
- 十分钟教会你们如何架设传奇
- 2017年09月19日泰国清迈曼谷普吉岛三地游
- USART_GetITStatus(USART3,USART_IT_IDLE) ==RESET进入中断问题
- python编程的就业方向_2021年python编程就业方向是怎样的?
- android 平板 办公,技德Remix超级平板如何让安卓系统成为办公神器
- navicat连接linux线上MySQL出现2003错误码
- ChatGpt 能取代人类吗?
- 由提交storm项目jar包引发对jar的原理的探索
- 马化腾徐少春等中国富豪获住房补贴 政策真空网民质疑
- 【iPhone/iPad】苹果iOS9正式版更新升级及固件刷机教程