備忘錄(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) 有一條連接線.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容