(18)行为型模式——备忘录

行为型模式——备忘录(Memento)

问题背景

当需要在不破坏封装性的前提下将对象的状态储存在外部对象中时,考虑使用备忘录。现在有一个可视化的界面编辑工具,用户能直接拖动预览窗口中的控件来改变控件的位置。如果用户对一次操作不满意,他应该可以撤销本次操作,即让编辑器的状态回滚到这次操作之前。

解决方案

为了实现撤销操作,我们必须在系统的某处记录用户每次操作前系统的状态。显然,让编辑器本身去记录这些状态有些不妥,因为这增加了类的职责。所以我们增加两个对象:备忘录和负责人。备忘录对象负责记录原始对象每一步的状态,负责人聚合了每一步操作所产生的备忘录,当原始对象撤销时,就从负责人中取出最近一条备忘录,将里面的状态赋值给当前状态。循环下去,就实现任意步的撤销操作。

但是这里有一个问题,为了不破坏类的封装性,外部应该无法访问备忘录的状态。但如果无法访问备忘录的状态,要怎么把它的状态赋值给原始对象呢?所以,实现备忘录需要一些特殊的语言机制支持,这就导致不同的语言实现备忘录的方式大相径庭。在C++中,可以把原始对象标记为备忘录的友元来实现对私有成员的访问,但在不支持友元的语言中,就要另想办法。

在C#中,既然没有友元的机制,就不可能用纯OO的方式实现备忘录。在大多数情况下,我们都不会在业务逻辑里使用反射,因为这会极大地增加代码的维护难度。但为了实现备忘录,我们只能让原始对象反射访问备忘录的私有成员。在标准的UML类图中,是无法表达“通过反射关联”这个语义的。因此我们扩展出一个“友元”语义:
友元
上图体现了两种自定义的关联关系:上面的由A指向B,表示A是B的友元;下面的是一个双向关联,表示A和B互为友元。

在这里,友元被定义为:如果A(通过某种方式)能够访问B的私有成员,则A是B的友元。

加入了友元的语义,我们就可以得到如下的程序结构:
程序结构

效果

  1. 保持了类的封装性。
  2. 简化了原始对象的职责。

缺陷

备忘录的实现对具体语言特性的依赖程度很高,不同的语言实现方法完全不同。同时,备忘录对象本身的开销并不小,大量生成备忘录对象会占用很大一部分内存空间。

相关模式

  1. 命令:撤销命令可以用备忘录实现。
  2. 迭代器:备忘录可以用于迭代器状态的回滚。

实现

using System;
using System.Collections.Generic;
using System.Reflection;

namespace Memento
{
    class Client
    {
        public class ControlA
        {
            private int positionX;
            private int positionY;
            private Caretaker caretaker = new Caretaker();
            public void Move(int dx, int dy)
            {
                caretaker.Push(new MementoA(positionX, positionY));
                Console.WriteLine($"移动: ({dx}, {dy})");
                positionX += dx;
                positionY += dy;
            }
            public void RollBack()
            {
                Console.WriteLine("回滚");
                var memento = caretaker.Pop();
                positionX = (int) typeof(MementoA).GetField("positionX", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(memento);
                positionY = (int) typeof(MementoA).GetField("positionY", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(memento);
            }
            public void Show()
            {
                Console.WriteLine($"位置: ({positionX}, {positionY})");
            }
        }

        public class MementoA
        {
            private int positionX;
            private int positionY;
            public MementoA(int x, int y)
            {
                positionX = x;
                positionY = y;
            }
        }

        public class Caretaker
        {
            private Stack<MementoA> stack = new Stack<MementoA>();
            public void Push(MementoA memento)
            {
                stack.Push(memento);
            }
            public MementoA Pop()
            {
                return stack.Pop();
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("创建一个控件...");
            var control = new ControlA();
            control.Show();
            control.Move(100, 200);
            control.Show();
            control.Move(500, -100);
            control.Show();
            control.RollBack();
            control.Show();
        }
    }

运行结果

发布了27 篇原创文章 · 获赞 41 · 访问量 2071

猜你喜欢

转载自blog.csdn.net/DIAX_/article/details/104290750