撤销和重做实现-第三部分(备忘录模式)
Posted 火柴盒zhang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了撤销和重做实现-第三部分(备忘录模式)相关的知识,希望对你有一定的参考价值。
本文是从英文的网站翻译,用作自己的整理和记录,有修改调整。水平有限,欢迎指正。原文地址:地址
一、引言
这是关于用C#编写多级撤销和重做实现的系列文章的第三部分。本系列展示了针对同一问题的三种方法的撤销/重做实现,以及我们如何使用这些方法实现不同场景的撤销/恢复。这些方法使用单对象状态变化、命令模式和备忘录模式。
正如我们所知道的,撤销/重做没有通用的解决方案,撤销/重做的实现对于每个应用程序都具有定制化部分。因此,本系列文章的每个部分首先讨论了该模式下的设计思路,然后在实例程序中展示了如何实现。该系列,撤销/重做操作使用三种不同的设计方式,因此您可以将每个模式实现与其他方法实现进行比较,并选择最适合您需求的方法。在每个部分文章中我们还讨论了每种方法的优缺点。
二、Undo/Redo实现的基本思路
我们知道,应用程序在每次操作后都会改变其状态。当应用程序操作运行时,它会改变其状态。因此,如果我们想Undo撤销,我们必须回到以前的状态。因此,为了能够恢复到以前的状态,我们需要存储应用程序运行时的状态。为了支持Redo重做,我们必须从当前状态转到下一个状态。
要实现Undo/Redo,我们必须存储应用程序的状态,并且必须转到上一状态进行撤销,转至下一状态进行重做。因此,我们必须维护应用程序的状态以支持Undo/Redo。为了在三种设计方法中维护应用程序的状态,我们使用了两个堆栈。一个堆栈包含撤消操作的状态,第二个堆栈包含重做操作的状态。撤消操作弹出撤消堆栈以获取以前的状态,并将以前的状态设置为应用程序。同样,重做操作弹出重做堆栈以获得下一个状态,并为应用程序设置下一状态。
现在我们知道,实现撤销-重做操作就是在应用程序的每个操作之后保持状态。现在的问题是,这种方法如何保持状态?在memento模式中,我们把容器的状态作为应用程序的状态。
三、备忘录模式通用思路
备忘录模式存储每次操作前的应用程序状态,以实现多级撤消/重做。要使用备忘录模式实现撤消/重做操作,备忘录memento 代表/表示容器对象的状态,MementoOriginator创建容器对象的memento(状态)。Caretaker 类将 memento (state)放入2个安全堆栈中,一个用于撤消,另一个用于重做,并返回撤消memento和重做memento。Undo/Redo类将使用Caretakerk类
获取Undo memento(状态)和Redo memento(状态),并执行undo/redo操作。
在以下步骤中讨论了如何使用备忘录模式设计思路:
3.1 第一步
确认要支持撤消/重做的容器。然后识别容器所持有的对象以及容器的属性,这些属性及对象可以在不同操作期间随时间变化。
3.2 第二步
然后,创建一个memento类来保存这些对象和容器的可变属性,以便该这个memento类可以代表容器的这种状态变化 。
3.3 第三步
然后创建一个MementoOriginator类,其职责是在任何点创建容器的memento 类并将其设置到容器中。这个类实现了两个方法getMemento()和setMemento(Memento Memento)。getMemento()方法生成容器的memento类并将其返回给调用方。SetMemento(Memento Memento)方法将 memento
(状态)设置为容器状态。SetMemento(Memento memento)
方法设置容器的 memento(State)。
3.4 第四步
当您在应用程序中执行不同的操作时,创建这个操作类型的Command类,
并将其push撤消/重做系统。当您需要执行undo 撤消操作时,只需从应用程序中调用UndoRedo类的undo方法,当您需要进行重做操作时,则只需从您的应用程序调用redo
重做操作。
然后创建一个Caretaker
类,该类将memento(状态)保存为两个堆栈。一个堆栈保存撤消操作的memento(状态),另一个堆栈保留重做操作的mement(状态)。它实现了三个方法getUndoMemento()、getRedoMemento()和InsertMementoForUndoRedo(Memento memento
)。GetUndoMemento()返回memento 类以便撤消操作。GetRedoMemento()返回memento 类以便重做操作。InsertMementoForUndoRedo(Memento Memento)将memento 插入Undo /Redo 管道plumbing 并清除重做堆栈。
3.5 第五步
然后生成实现以下IUndoRedo接口的Undo/Redo类:
interface IUndoRedo
void Undo(int level);
void Redo(int level);
void SetStateForUndoRedo();
3.5.1在Undo操作中:
- 从Caretaker 类中获取UndoMemento
- 然后使用
MementoOriginator类设置容器的
Undomemento
3.5.2在Redo操作中:
- 从Caretaker 类中获取
RedoMemento
- 然后使用
MementoOriginator类设置容器的REdomemento
3.5.3在SetStateForUndoRedo
操作中:
- 从
MementoOriginator
类中获取当前memento (state)
- 然后把当前
memento (state)新增到Caretaker
,以支持Undo/Redo plumbing
3.6 第六步
在应用程序的每个操作之后,调用UndoRedo类的方法SetStateForUndoRed0(),以启用该操作Undo-Redo。
四、程序实现
下面讨论了使用备忘录模式示例应用程序的撤消/重做实现:
5.1 第一步
在这里,画布容器是保存Uielement对象,容器的属性在不同操作期间会随时间变化。
5.2 第二步
现在,我们将创建以下memento类,并将Uielement对象保存为画布状态。
public class Memento
private List<UIElement> _ContainerState;
public List<UIElement> ContainerState
get return _ContainerState;
public Memento(List<UIElement> containerState)
this._ContainerState = containerState;
现在这个memento 类就代表了画布的状态。
5.3 第三步
然后创建以下MementoOriginator 类,它使用深度复制depp copy画布容器下的对象并返回 memento 对象
public class MementoOriginator
private Canvas _Container;
public MementoOriginator(Canvas container)
_Container = container;
public Memento getMemento()
List<UIElement> _ContainerState = new List<UIElement>();
foreach (UIElement item in _Container.Children)
if (!(item is Thumb))
UIElement newItem = DeepClone(item);
_ContainerState.Add(newItem);
return new Memento(_ContainerState);
public void setMemento(Memento memento)
_Container.Children.Clear();
Memento memento1 = MementoClone(memento);
foreach (UIElement item in memento1.ContainerState)
((Shape)item).Stroke = System.Windows.Media.Brushes.Black;
_Container.Children.Add(item);
public Memento MementoClone(Memento memento)
List<UIElement> _ContainerState = new List<UIElement>();
foreach (UIElement item in memento.ContainerState)
if (!(item is Thumb))
UIElement newItem = DeepClone(item);
_ContainerState.Add(newItem);
return new Memento(_ContainerState);
private UIElement DeepClone(UIElement element)
string shapestring = XamlWriter.Save(element);
StringReader stringReader = new StringReader(shapestring);
XmlTextReader xmlTextReader = new XmlTextReader(stringReader);
UIElement DeepCopyobject = (UIElement)XamlReader.Load(xmlTextReader);
return DeepCopyobject;
GetMemento()方法深度复制画布的UIelement集合到memento ,并将memento返回给调用方。
SetMemento(Memento Memento)方法首先清除画布集合,然后通过将参数Memento的每个对象添加到画布中。
DeepClone(UIElement元素)方法只是深度复制 UIElement对象。
5.4 第四步
下面Caretaker
类将memento(状态)保存为两个堆栈。撤消堆栈保存撤消操作的内存(状态)Memento (state)
,重做堆栈保存重做操作的内存 memento (state)
。
class Caretaker
private Stack<Memento> UndoStack = new Stack<Memento>();
private Stack<Memento> RedoStack = new Stack<Memento>();
public Memento getUndoMemento()
if (UndoStack.Count >= 2)
RedoStack.Push(UndoStack.Pop());
return UndoStack.Peek();
else
return null;
public Memento getRedoMemento()
if (RedoStack.Count != 0)
Memento m = RedoStack.Pop();
UndoStack.Push(m);
return m;
else
return null;
public void InsertMementoForUndoRedo(Memento memento)
if (memento != null)
UndoStack.Push(memento);
RedoStack.Clear();
public bool IsUndoPossible()
if (UndoStack.Count >= 2)
return true;
else
return false;
public bool IsRedoPossible()
if (RedoStack.Count != 0)
return true;
else
return false;
5.5 第五步
下面是实现UndoRedo
类
public class UndoRedo : IUndoRedo
Caretaker _Caretaker = new Caretaker();
MementoOriginator _MementoOriginator = null;
public event EventHandler EnableDisableUndoRedoFeature;
public UndoRedo(Canvas container)
_MementoOriginator = new MementoOriginator(container);
public void Undo(int level)
Memento memento = null;
for (int i = 1; i <= level; i++)
memento = _Caretaker.getUndoMemento();
if (memento != null)
_MementoOriginator.setMemento(memento);
if (EnableDisableUndoRedoFeature != null)
EnableDisableUndoRedoFeature(null, null);
public void Redo(int level)
Memento memento = null;
for (int i = 1; i <= level; i++)
memento = _Caretaker.getRedoMemento();
if (memento != null)
_MementoOriginator.setMemento(memento);
if (EnableDisableUndoRedoFeature != null)
EnableDisableUndoRedoFeature(null, null);
public void SetStateForUndoRedo()
Memento memento = _MementoOriginator.getMemento();
_Caretaker.InsertMementoForUndoRedo(memento);
if(EnableDisableUndoRedoFeature != null)
EnableDisableUndoRedoFeature(null,null);
public bool IsUndoPossible()
return _Caretaker.IsUndoPossible();
public bool IsRedoPossible()
return _Caretaker.IsRedoPossible();
在Undo方法中,我们执行Undo操作到制定的级别。在每次 undo撤销操作中,我们都从 Caretaker类
获得UndoMemento
,并通MementoOriginator类的将UndoMemento
设置到画布上。在Redo
方法中,我们执行 RedoOperation
到制定的级别。在每次RedoOperation
中,我们从Caretaker
处获得RedoMemento
,并使用MementoOriginator类
将RedoMemento
设置到画布上。在SetStateForUndoRedo操作中,我们使用MementoOriginator获取当前memento(状态),然后将当前memento(状态)插入看守器中,以支持撤消/重做管道。
5.6 第六步
在该应用程序操作之后,我们调用了UndoRedo类的SetStateForUndoRed0()方法。当从UI单击Undo时,我们调用UndoRedo类的Undo方法,当从UI中单击Redo时,我们将调用UndoRedo类的Redo方法。
在这里,没有明确设置撤消堆栈和重做堆栈的大小,保存的撤消重做状态的数量取决于系统内存。
六、优势和缺点
6.1 优点
在memento模式中,我们保持容器的状态,这是内存密集型的。在这里,您必须对容器保存的所有对象和属性进行深度复制。如果您无法为其中任何一个创建深度副本,则可能会出现问题。
6.2 缺点
内存密集型的memory intensive。
以上是关于撤销和重做实现-第三部分(备忘录模式)的主要内容,如果未能解决你的问题,请参考以下文章