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


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

    一、引言

            这是关于用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类:

    1. interface IUndoRedo
    2. {
    3. void Undo(int level);
    4. void Redo(int level);
    5. void SetStateForUndoRedo();
    6. }

    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对象保存为画布状态。

    1. public class Memento
    2. {
    3. private List _ContainerState;
    4. public List ContainerState
    5. {
    6. get { return _ContainerState; }
    7. }
    8. public Memento(List containerState)
    9. {
    10. this._ContainerState = containerState;
    11. }
    12. }

            现在这个memento 类就代表了画布的状态。

    5.3 第三步

             然后创建以下MementoOriginator 类,它使用深度复制depp copy画布容器下的对象并返回 memento 对象  

    1. public class MementoOriginator
    2. {
    3. private Canvas _Container;
    4. public MementoOriginator(Canvas container)
    5. {
    6. _Container = container;
    7. }
    8. public Memento getMemento()
    9. {
    10. List _ContainerState = new List();
    11. foreach (UIElement item in _Container.Children)
    12. {
    13. if (!(item is Thumb))
    14. {
    15. UIElement newItem = DeepClone(item);
    16. _ContainerState.Add(newItem);
    17. }
    18. }
    19. return new Memento(_ContainerState);
    20. }
    21. public void setMemento(Memento memento)
    22. {
    23. _Container.Children.Clear();
    24. Memento memento1 = MementoClone(memento);
    25. foreach (UIElement item in memento1.ContainerState)
    26. {
    27. ((Shape)item).Stroke = System.Windows.Media.Brushes.Black;
    28. _Container.Children.Add(item);
    29. }
    30. }
    31. public Memento MementoClone(Memento memento)
    32. {
    33. List _ContainerState = new List();
    34. foreach (UIElement item in memento.ContainerState)
    35. {
    36. if (!(item is Thumb))
    37. {
    38. UIElement newItem = DeepClone(item);
    39. _ContainerState.Add(newItem);
    40. }
    41. }
    42. return new Memento(_ContainerState);
    43. }
    44. private UIElement DeepClone(UIElement element)
    45. {
    46. string shapestring = XamlWriter.Save(element);
    47. StringReader stringReader = new StringReader(shapestring);
    48. XmlTextReader xmlTextReader = new XmlTextReader(stringReader);
    49. UIElement DeepCopyobject = (UIElement)XamlReader.Load(xmlTextReader);
    50. return DeepCopyobject;
    51. }
    52. }

            GetMemento()方法深度复制画布的UIelement集合到memento ,并将memento返回给调用方。

            SetMemento(Memento Memento)方法首先清除画布集合,然后通过将参数Memento的每个对象添加到画布中。

            DeepClone(UIElement元素)方法只是深度复制 UIElement对象。

    5.4 第四步 

            下面Caretaker类将memento(状态)保存为两个堆栈。撤消堆栈保存撤消操作的内存(状态)Memento (state),重做堆栈保存重做操作的内存 memento (state)

    1. class Caretaker
    2. {
    3. private Stack UndoStack = new Stack();
    4. private Stack RedoStack = new Stack();
    5. public Memento getUndoMemento()
    6. {
    7. if (UndoStack.Count >= 2)
    8. {
    9. RedoStack.Push(UndoStack.Pop());
    10. return UndoStack.Peek();
    11. }
    12. else
    13. return null;
    14. }
    15. public Memento getRedoMemento()
    16. {
    17. if (RedoStack.Count != 0)
    18. {
    19. Memento m = RedoStack.Pop();
    20. UndoStack.Push(m);
    21. return m;
    22. }
    23. else
    24. return null;
    25. }
    26. public void InsertMementoForUndoRedo(Memento memento)
    27. {
    28. if (memento != null)
    29. {
    30. UndoStack.Push(memento);
    31. RedoStack.Clear();
    32. }
    33. }
    34. public bool IsUndoPossible()
    35. {
    36. if (UndoStack.Count >= 2)
    37. {
    38. return true;
    39. }
    40. else
    41. return false;
    42. }
    43. public bool IsRedoPossible()
    44. {
    45. if (RedoStack.Count != 0)
    46. {
    47. return true;
    48. }
    49. else
    50. return false;
    51. }
    52. }

    5.5 第五步 

            下面是实现UndoRedo

    1. public class UndoRedo : IUndoRedo
    2. {
    3. Caretaker _Caretaker = new Caretaker();
    4. MementoOriginator _MementoOriginator = null;
    5. public event EventHandler EnableDisableUndoRedoFeature;
    6. public UndoRedo(Canvas container)
    7. {
    8. _MementoOriginator = new MementoOriginator(container);
    9. }
    10. public void Undo(int level)
    11. {
    12. Memento memento = null;
    13. for (int i = 1; i <= level; i++)
    14. {
    15. memento = _Caretaker.getUndoMemento();
    16. }
    17. if (memento != null)
    18. {
    19. _MementoOriginator.setMemento(memento);
    20. }
    21. if (EnableDisableUndoRedoFeature != null)
    22. {
    23. EnableDisableUndoRedoFeature(null, null);
    24. }
    25. }
    26. public void Redo(int level)
    27. {
    28. Memento memento = null;
    29. for (int i = 1; i <= level; i++)
    30. {
    31. memento = _Caretaker.getRedoMemento();
    32. }
    33. if (memento != null)
    34. {
    35. _MementoOriginator.setMemento(memento);
    36. }
    37. if (EnableDisableUndoRedoFeature != null)
    38. {
    39. EnableDisableUndoRedoFeature(null, null);
    40. }
    41. }
    42. public void SetStateForUndoRedo()
    43. {
    44. Memento memento = _MementoOriginator.getMemento();
    45. _Caretaker.InsertMementoForUndoRedo(memento);
    46. if(EnableDisableUndoRedoFeature != null)
    47. {
    48. EnableDisableUndoRedoFeature(null,null);
    49. }
    50. }
    51. public bool IsUndoPossible()
    52. {
    53. return _Caretaker.IsUndoPossible();
    54. }
    55. public bool IsRedoPossible()
    56. {
    57. return _Caretaker.IsRedoPossible();
    58. }
    59. }

            在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。

  • 相关阅读:
    如何分析周活跃率?
    【目标跟踪】pytorch实现DeepSORT+YOLOV5 YOLOFastestv2 含代码
    CRGDFPASSC,CAS号:166184-23-2
    Zookeeper集群搭建及原理
    根目录/ 空间不够,扩容,导致web页面无法加载问题
    ElasticSearch基本用法
    golang通过gorm操作sqlite设置主键自增
    无线充U型超声波电动牙刷方案开发
    d应扩展__traits(parameters)的使用范围
    webserver项目
  • 原文地址:https://blog.csdn.net/likewindy/article/details/126802247