撤销和重做实现-第三部分(备忘录模式)

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。

以上是关于撤销和重做实现-第三部分(备忘录模式)的主要内容,如果未能解决你的问题,请参考以下文章

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

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

撤销和重做实现-第一部分(单对象状态变化)

撤销和重做实现-第一部分(单对象状态变化)

Memento模式(备忘录设计模式)

带有 Relay-GraphQL 突变的撤销-重做状态遍历模式