技术开发 频道

C#中使用备忘录模式实现Undo/Redo

  【IT168 专稿】

  关于Undo/Redo实现的基本思想

  正如我们所知,应用程序在每次操作后改变其状态。当操作应用程序时,它的状态会发生改变。所以,若有人想要做撤销,他不得不回到先前的状态。因此,为了能够回到先前状态,我们需要在应用程序运行时存储它的状态。要支持重做,我们不得不从目前状态跳到下一个状态。

  为了实现Undo/Redo,我们不得不存储应用程序的状态并在撤销时跳到前一个状态而在重做时跳到下一个状态。因此我们需要维护应用程序的状态来支持Undo/Redo。在所有三种方法中,应用程序状态的维护用到了两个栈。一个栈包含用于撤销操作的状态,第二个包含用于重做的状态。撤销操作弹出撤销栈以获取前一个状态并将其设置给应用程序。同样的,重做操作弹出重做栈以获取下一个状态并将其设置给应用程序。

  现在,我们知道了Undo/Redo的实现操作都是关于保持应用程序每次操作后的状态。现在的问题是该方法如何保存状态。在备忘录模式中,我们将保持容器的状态作为应用程序的状态。

  第一篇:C#中使用单个对象的方法实现Undo/Redo

  第二篇:C#中使用命令模式实现Undo/Redo

  关于备忘录模式

  这里我并不打算讨论备忘录模式,你可从这和这了解该模式相关信息。

  如何应用备忘录模式对任意应用程序Undo/Redo操作建模?

  备忘录模式在每个操作执行多级撤销/重做前存储应用程序的状态。为了使用备忘录模式实现撤销/重做操作,备忘录代表了Container对象的状态,而MementoOriginator创建了一个Container对象的备忘录(状态)。Caretaker将备忘录(状态)安全保存在两个栈中,一个用于撤销而另一个用于重做,并且它返回撤销备忘录和重做备忘录。一个撤销/重做类使用Caretaker来获得撤销备忘录(状态)和重做备忘录(状态),并完成撤销/重做操作。

  备忘录模式如何对任意应用程序Undo/Redo操作建模将在以下步骤中讨论:

  步骤1

  识别出你将要支持Undo/Redo操作的容器,然后识别出容器包含的对象以及可在不同的操作期间变化的容器的属性。

  步骤2

  创建一个备忘录类来包含这些对象和容器的可变属性,因此这个备忘录类可以代表容器上不同操作后容器的状态。

  步骤3

  然后创建MementoOriginator类,它负责创建任何时刻的容器备忘录并将其设置给容器。该类实现了两个方法getMemento()和setMemento(Memento memento)。getMemento()方法创建容器的备忘录并将其放回给调用方。而SetMemento(Memento memento)方法将一个memento(State) 设置给容器。

  步骤4

  然后创建保存memento(state)在两个栈中的Caretaker类。一个栈为Undo操作保存memento (state),而另一个为Redo操作保存memento (state)。该类实现了三个方法getUndoMemento (),getRedoMemento (),InsertMementoForUndoRedo (Memento memento)。GetUndoMemento ()为撤销操作返回一个备忘录。GetRedoMemento()为重做操作返回一个备忘录。InsertMementoForUndoRedo(Memento memento)将备忘录插入Undo/Redo管道并清空Redo栈。

  步骤5

  下面给出实现以下IUndoRedo接口的Undo/Redo类:

Collapse  Copy Code
  
interface IUndoRedo
    {
        
void Undo(int level);
        
void Redo(int level);
        
void SetStateForUndoRedo();
    }

  在Undo操作中:
  •首先从Caretaker获取UndoMemento
  •然后利用MementoOriginator把Undomemento设置给容器

  在Redo操作中:
  •首先从Caretaker获取RedoMemento
  •然后利用MementoOriginator把REdomemento设置给容器

  在SetStateForUndoRedo操作中:
  •利用MementoOriginator获取当前memento (state)
  •将当memento (state)前插入Caretaker以支持Undo/Redo

  步骤6

  在你应用程序每次操作后,调用UndoRedo类的SetStateForUndoRedo()方法来使Undo Redo操作可用。

  示例应用程序描述

  这个示范WPF绘制应用程序用来作为结合Undo/Redo操作的案例。该WPF应用程序示例支持四种操作:插入对象、删除对象、移动对象和调整对象的尺寸,它还有两种类型的几何对象:矩形和多边形。它使用画布作为包含这些几何对象的容器。

  现在,在此系列文章中,我们可以看到如何让这四个操作支持Undo/Redo。在第一部分,使用单个对象表示变化的方法实现。在第二部分,使用命令模式实现而在第三部分,使用备忘录模式实现。

  使用备忘录模式实现示例应用程序的Undo/Redo

  步骤1

  这里的容器是包含Uielement对象的画布,而在不同操作期间,容器没有属性发生改变。

  步骤2

  现在,我们将使下面的memento类包含Uielement对象作为画布的状态。

Collapse  Copy Code
  
public class Memento
    {
        
private List<UIElement> _ContainerState;

        
public List<UIElement> ContainerState
        {
            
get { return _ContainerState; }
        }
        
public Memento(List<UIElement> containerState)
        {
            
this._ContainerState = containerState;
        }
    }

  现在这个备忘录代表了画布的状态。

  步骤3

  接下来的MementoOriginator利用深度拷贝画布的对象创建其备忘录并将备忘录设置给画布。

Collapse  Copy Code
  
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集合来创建一个备忘录并将其放回给调用方。SetMemento(Memento memento)方法首先清空画布然后通过将备忘录的每个对象添加给Canvas来设置备忘录。DeepClone(UIElement element)方法仅创建一个UIelement对象的深度拷贝。

  步骤4

  下面的Caretaker类将memento (state)保存在两个栈中。Undo栈为Undo操作保存Memento (state),而Redo栈为/Redo操作保存Memento (state)。

Collapse  Copy Code
  
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

  下面是UndoRedo类的实现:

Collapse  Copy Code
    
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方法中,我们就执行UndoOperation多少次。在每次的Undo操作中,我们从Caretaker获取UndoMemento并利用MementoOriginator将Undomemento设置给画布。同样的,在Redo方法中,我们就执行RedoOperation多少次。在每次的RedoOperation中,我们从Caretaker获取RedoMemento并利用MementoOriginator获取当前备忘录(状态),然后将当前备忘录(状态)插入到Caretaker以支持Undo/Redo。

  步骤6

  在该应用程序每次操作后,我们调用UndoRedo类的SetStateForUndoRedo()方法使UndoRedo操作可用。当用户界面上点击Undo时,我们调用UndoRedo类的Undo方法而当用户界面上点击Redo时,我们调用UndoRedo类的redo方法。

  这里,我们没有明确设置Undo栈和Redo栈的大小,因此,应用程序能具有的状态数目取决于系统的内存。

  使用备忘录模式时的变更管理

  在备忘录模式中,如果你想要添加新的操作,一般来说你不需要在Undo/Redo代码中作任何改动就可以使得操作能Undo/Redo。因为在备忘录模式中,我们保存了整个应用程序状态的深度拷贝。
使用备忘录模式的优缺点

  它的优点在于其变更管理的表现非常好。

  由于在备忘录模式中,我们保存了容器的状态,因此它是内存密集型的。这里你不得不对所有的对象和容器具有的属性进行深度拷贝。若你不能对其中任一个进行深度拷贝时将出现问题。

1
相关文章