備忘錄(Memento)

意圖

在不破壞封裝性的前提下涂滴,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài)缔杉。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)搁料。

結(jié)構(gòu)

備忘錄結(jié)構(gòu)圖

以及它們之間的協(xié)作關(guān)系:

備忘錄協(xié)作圖

動機(jī)

記錄一個(gè)對象的內(nèi)部狀態(tài)。允許用戶在必要的時(shí)候霸琴,通過恢復(fù)其狀態(tài)來取消不確定的操作或從錯誤中恢復(fù)過來昭伸。

備忘錄模式用一個(gè)備忘錄(Memento)對象存儲原發(fā)器(Originator)對象在某個(gè)瞬間的內(nèi)部狀態(tài)。在具體實(shí)現(xiàn)上庐杨,盡量(有些語言不支持)做到只有原發(fā)器可以向備忘錄中存取信息,備忘錄對其他對象 “不可見”仁堪。

適用性

  • 必須保存一個(gè)對象在某一個(gè)時(shí)刻的狀態(tài)(或部分狀態(tài)), 這樣以后需要時(shí)它才能恢復(fù)到先前的狀態(tài)各吨;
  • 如果通過接口讓其它對象直接得到這些私密的狀態(tài),又會暴露對象的實(shí)現(xiàn)細(xì)節(jié)并破壞對象的封裝性揭蜒;

注意事項(xiàng)

  • 使用備忘錄可以避免暴露那些只應(yīng)由原發(fā)器管理卻又必須存儲在原發(fā)器之外的信息;
  • 原先徙融,原發(fā)器需保留所有請求的內(nèi)部狀態(tài)版本。現(xiàn)在欺冀,只需保留當(dāng)前請求的內(nèi)部狀態(tài)版本萨脑,簡化了原發(fā)器的設(shè)計(jì);
  • 如何保證只有原發(fā)器才能訪問備忘錄的狀態(tài)渤早,避免信息變相泄露
  • 能否通過增量式(存儲)解決備忘錄的各種開銷悴灵,如存儲開銷骂蓖、復(fù)制開銷等。


示例

模擬一個(gè)圖形編輯器登下,它使用MoveCommand命令對象來執(zhí)行(或回退)一個(gè)Graphic圖形對象從一個(gè)位置到另一個(gè)位置的變換。

實(shí)現(xiàn)(C#)

結(jié)構(gòu)圖
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;}
    // 當(dāng)前位置
    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);
    }
}

// 客戶端移動命令,負(fù)責(zé)在外部保存?zhèn)渫浶畔ⅰ?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
{
    // 只允許原發(fā)器(ConstraintSolver)訪問備忘錄(ConstraintSolverMemento)的內(nèi)部信息揩瞪。
    private List<Tuple<Graphic,Graphic>> Paths { get; set;}

    // 原發(fā)器類(圖塊之間的線路關(guān)系)
    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) 有一條連接線.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刃唤,一起剝皮案震驚了整個(gè)濱河市白群,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帜慢,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侍咱,死亡現(xiàn)場離奇詭異密幔,居然都是意外死亡楔脯,警方通過查閱死者的電腦和手機(jī)胯甩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來木柬,“玉大人淹办,你說我怎么就攤上這事眉枕×” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵姥宝,是天一觀的道長恐疲。 經(jīng)常有香客問我套么,道長,這世上最難降的妖魔是什么违诗? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任疮蹦,我火速辦了婚禮茸炒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘壁公。我一直安慰自己,他們只是感情好紊册,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芳绩,像睡著了一般撞反。 火紅的嫁衣襯著肌膚如雪妥色。 梳的紋絲不亂的頭發(fā)上遏片,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音笔呀,去河邊找鬼。 笑死许师,一個(gè)胖子當(dāng)著我的面吹牛僚匆,可吹牛的內(nèi)容都是我干的枯跑。 我是一名探鬼主播白热,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纳击!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起焕数,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎识脆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灼捂,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡换团,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艘包。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卦尊,死狀恐怖舌厨,靈堂內(nèi)的尸體忽然破棺而出猫牡,到底是詐尸還是另有隱情邓线,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布震庭,位于F島的核電站,受9級特大地震影響器联,放射性物質(zhì)發(fā)生泄漏婿崭。R本人自食惡果不足惜拨拓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一氓栈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧醋界,春花似錦竟宋、人聲如沸形纺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜗字。三九已至,卻和暖如春脂新,著一層夾襖步出監(jiān)牢的瞬間挪捕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工戏羽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人楼吃。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓始花,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孩锡。 傳聞我的和親對象是個(gè)殘疾皇子酷宵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1 場景問題# 1.1 開發(fā)仿真系統(tǒng)## 考慮這樣一個(gè)仿真應(yīng)用躬窜,功能是:模擬運(yùn)行針對某個(gè)具體問題的多個(gè)解決方案浇垦,記...
    七寸知架構(gòu)閱讀 2,135評論 1 50
  • 備忘錄模式(Memento)是指在不破壞封裝性的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài)荣挨,并在該對象之外保存這個(gè)狀態(tài)男韧,這樣以...
    FlyElephant閱讀 752評論 0 0
  • “狀態(tài)變化”模式 在組建構(gòu)建過程中,某些對象的狀態(tài)經(jīng)常面臨變化默垄,如何對這些變化進(jìn)行有效的管理此虑?同時(shí)又維持高層模塊的...
    故事狗閱讀 621評論 0 0
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法口锭,內(nèi)部類的語法朦前,繼承相關(guān)的語法,異常的語法鹃操,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • 模式定義: 在不破環(huán)封裝行性的前提下韭寸,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài)荆隘。這樣就可以將該對象恢復(fù)到...
    忘凈空閱讀 1,968評論 3 0