备忘录(Memento)

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

结构

备忘录结构图

以及它们之间的协作关系:

备忘录协作图

动机

记录一个对象的内部状态。允许用户在必要的时候,通过恢复其状态来取消不确定的操作或从错误中恢复过来。

备忘录模式用一个备忘录(Memento)对象存储原发器(Originator)对象在某个瞬间的内部状态。在具体实现上,尽量(有些语言不支持)做到只有原发器可以向备忘录中存取信息,备忘录对其他对象 “不可见”。

适用性

  • 必须保存一个对象在某一个时刻的状态(或部分状态), 这样以后需要时它才能恢复到先前的状态;
  • 如果通过接口让其它对象直接得到这些私密的状态,又会暴露对象的实现细节并破坏对象的封装性;

注意事项

  • 使用备忘录可以避免暴露那些只应由原发器管理却又必须存储在原发器之外的信息;
  • 原先,原发器需保留所有请求的内部状态版本。现在,只需保留当前请求的内部状态版本,简化了原发器的设计;
  • 如何保证只有原发器才能访问备忘录的状态,避免信息变相泄露
  • 能否通过增量式(存储)解决备忘录的各种开销,如存储开销、复制开销等。


示例

模拟一个图形编辑器,它使用MoveCommand命令对象来执行(或回退)一个Graphic图形对象从一个位置到另一个位置的变换。

实现(C#)

结构图
using System;
using System.Collections.Generic;


// 位置
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public override string ToString()
    {
        return string.Format("({0},{1})", this.X, this.Y);
    }

    public static Point operator -(Point obj)
    {
        return new Point{ X = -obj.X, Y = -obj.Y };
    }

    public static Point operator -(Point obj1, Point obj2)
    {
        return new Point{ X = obj1.X - obj2.X, Y = obj1.Y - obj2.Y };
    }

    public static bool operator ==(Point obj1, Point obj2)
    {
        if(obj1 != null && obj2 != null)
        {
            return obj1.Equals(obj2);
        }

        return false;
    }

    public static bool operator !=(Point obj1, Point obj2)
    {
        if(obj1 != null && obj2 != null)
        {
            return !obj1.Equals(obj2);
        }

        return false;
    }

    public override bool Equals(object obj)
    {
        if(obj is Point)
        {
            Point obj2 = (Point)obj;

            return this.X == obj2.X && this.Y == obj2.Y;
        }

        return false;
    }

    public override int GetHashCode()
    {
        return this.X.GetHashCode() ^ this.Y.GetHashCode();
    }
}

// 图块类
public class Graphic
{
    // 图块名称
    public string Name { get; private set;}
    // 当前位置
    public Point Position {get; private set;}

    public Graphic(string name, Point position)
    {
        this.Name = name;
        this.Position = position;
    }

    public void Move(Point delta)
    {
        bool flag = delta.X < 0 || delta.Y < 0;

        delta.X += Position.X;
        delta.Y += Position.Y;
        Console.WriteLine("{0} 从 {1} {3}到 {2}", this.Name, this.Position, delta, (flag ? "撤销" : "移动"));
        Position = delta;

        ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
        solver.Update(this);
    }


    public static bool operator ==(Graphic obj1, Graphic obj2)
    {
        if(obj1 != null && obj2 != null)
        {
            return obj1.Equals(obj2);
        }

        return false;
    }

    public static bool operator !=(Graphic obj1, Graphic obj2)
    {
        if(obj1 != null && obj2 != null)
        {
            return !obj1.Equals(obj2);
        }

        return false;
    }

    public override bool Equals(object obj)
    {   

        if(obj != null && obj is Graphic) 
        {
            Graphic obj2 = (obj as Graphic) ;

            return this.Name == obj2.Name && this.Position == obj2.Position;
        }
        
        return false;

    }

    public override string ToString()
    {
        return string.Format("{0}{1}", this.Name, this.Position);
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode() ^ this.Position.GetHashCode();
    }

    public Graphic Clone()
    {
        return new Graphic(this.Name, this.Position);
    }
}

// 客户端移动命令,负责在外部保存备忘录信息。
public sealed class MoveCommand
{
    private ConstraintSolverMemento memento;
    private Point delta;
    private Graphic target;

    public MoveCommand(Graphic target, Point delta)
    {
        this.target = target;
        this.delta = delta;
    }

    public void Execute()
    {
        ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
        this.memento = solver.CreateMemento();
        this.target.Move(delta);
        solver.Solve();
    }

    public void Unexecute()
    {
        ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
        target.Move(-this.delta);
        solver.SetMemento(this.memento);
        solver.Solve();
    }

}


// 备忘录类
public class ConstraintSolverMemento
{
    // 只允许原发器(ConstraintSolver)访问备忘录(ConstraintSolverMemento)的内部信息。
    private List<Tuple<Graphic,Graphic>> Paths { get; set;}

    // 原发器类(图块之间的线路关系)
    public class ConstraintSolver
    {
        private static ConstraintSolver instance;
        private List<Tuple<Graphic,Graphic>> Paths { get; set;}

        // 单例模式(不考虑线程安全)。
        public static ConstraintSolver GetInstance()
        {
            if (instance == null)
            {
                instance = new ConstraintSolver();
            }

            return instance;
        }

        private ConstraintSolver() {}

        public void Update(Graphic graphic)
        {
            Tuple<Graphic,Graphic> find = null;
            foreach(Tuple<Graphic,Graphic> item in Paths)
            {
                if(item.Item1.Name == graphic.Name || item.Item2.Name == graphic.Name)
                {
                    find = item;
                    break;
                }
            }

            if(find != null)
            {
                this.Paths.Remove(find);
                if(find.Item1.Name == graphic.Name)
                {
                    this.Paths.Add(new Tuple<Graphic,Graphic>(graphic.Clone(), find.Item2.Clone()));
                }
                else
                    this.Paths.Add(new Tuple<Graphic,Graphic>(find.Item1.Clone(), graphic.Clone()));
            }
        }

        public void AddConstraint(Graphic start, Graphic end)
        {
            if(this.Paths == null) this.Paths = new List<Tuple<Graphic,Graphic>> ();

            foreach(Tuple<Graphic,Graphic> item in Paths)
            {
                if(item.Item1 == start && item.Item2 == end) return;
            }

            Paths.Add(new Tuple<Graphic,Graphic>(start.Clone(),end.Clone()));
        }

        public void RemoveConstraint(Graphic start, Graphic end)
        {
            foreach(Tuple<Graphic,Graphic> item in Paths)
            {
                if(item.Item1 == start && item.Item2 == end) Paths.Remove(item);
            }
        }

        public ConstraintSolverMemento CreateMemento()
        {       
            return new ConstraintSolverMemento { Paths = this.Paths };
        }

        public void SetMemento(ConstraintSolverMemento memento)
        {
            this.Paths = memento.Paths;
        }

        // 绘画图块之间的线路
        public void Solve()
        {
            foreach(Tuple<Graphic,Graphic> item in this.Paths)
            {
                Console.WriteLine("   路线打印:{0} 到 {1} 有一条连接线.", item.Item1, item.Item2);
            }
            Console.WriteLine();
        }

    }

}


// 测试。
public class App
{
    public static void Main(string[] args)
    {
        Graphic g1 = new Graphic("A", new Point { X = 0, Y = 0});
        Graphic g2 = new Graphic("B", new Point { X = 20, Y = 0});
        MoveCommand command1 =  new MoveCommand(g1, new Point { X = 15, Y = 0 });
        MoveCommand command2 =  new MoveCommand(g2, new Point { X = 45, Y = 0 });

        ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
        solver.AddConstraint(g1,g2);

        Console.WriteLine("初始位置:图块{0},图块{1}\n", g1,g2);
        command1.Execute();
        command2.Execute();

        command2.Unexecute();
        command1.Unexecute();
    }
}

// 控制台输出:
//  初始位置:图块A(0,0),图块B(20,0)

//  A 从 (0,0) 移动到 (15,0)
//     路线打印:A(15,0) 到 B(20,0) 有一条连接线.

//  B 从 (20,0) 移动到 (65,0)
//     路线打印:A(15,0) 到 B(65,0) 有一条连接线.

//  B 从 (65,0) 撤销到 (20,0)
//     路线打印:A(15,0) 到 B(20,0) 有一条连接线.

//  A 从 (15,0) 撤销到 (0,0)
//     路线打印:A(0,0) 到 B(20,0) 有一条连接线.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容