• 20.Memento备忘录(行为型模式)


    一:动机(Motivation)

    <1>在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。

    <2>如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。

    二:意图(Intent)

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

    三:结构(Structure)

    在这里插入图片描述

    四:结构详解

    在这里插入图片描述

    五:生活中的例子

    <1>玩游戏(单机)时,需要经常保存进度,在必要的时候(如gameover),再读取存档,继续游戏(即S/L大法)。

    <2>三个对象(与下图对应关系):
    游戏 = 游戏角色
    进度 = 角色状态存储箱
    存取档工具 = 角色状态管理者

    六:实现

    namespace Test
    {
        public class 游戏进度
        {
            public 游戏进度(int attack, int life, int defense)
            {
                this.attack = attack;
                this.life = life;
                this.defense = defense;
                this.createTime = DateTime.Now;
            }
    
            private int attack;
            public int Attack { get { return attack; } }
    
            private int life;
            public int Life { get { return life; } }
    
            private int defense;
            public int Defense { get { return defense; } }
    
            private DateTime createTime;
            public DateTime CreateTime { get { return createTime; } }
    
            public override string ToString()
            {
                return String.Format("{0},生命:{1},攻击:{2},防御:{3}",
                    this.CreateTime, this.Life, this.Attack, this.Defense);
            }
        }
    
        public class 游戏
        {
            public 游戏()
            {
                this.attack = 100;
                this.life = 100;
                this.defense = 100;
                Console.WriteLine("游戏开始于:{0}", DateTime.Now);
            }
    
            private int attack;
            public int Attack
            {
                get { return this.attack; }
            }
    
            private int life;
            public int Life
            {
                get { return this.life; }
            }
    
            private int defense;
            public int Defense
            {
                get { return this.defense; }
            }
            public void 查看状态()
            {
                Console.WriteLine("当前生命力:{0},攻击力:{1},防御力:{2},游戏是否结束:{3}",
                    this.Life, this.Attack, this.Defense, this.游戏结束);
            }
    
            public void 战斗()
            {
                Console.WriteLine("\t开始战斗!");
                System.Threading.Thread.Sleep(3000);
                int lifeless = new Random().Next(100);
                this.life = this.life - lifeless;
                int attackless = new Random().Next(100);
                this.attack = this.attack - attackless;
                int defenseless = new Random().Next(100);
                this.defense = this.defense - defenseless;
                Console.WriteLine("本回合战斗结束,损失:生命:{0},攻击:{1},防御:{2}",
                    this.Life, this.Attack, this.Defense);
            }
    
            public bool 游戏结束
            {
                get { return this.Life <= 0; }
            }
    
            public 游戏进度 存档()
            {
                return new 游戏进度(this.attack, this.life, this.defense);
            }
    
            public void 读档(游戏进度 saved)
            {
                this.life = saved.Life;
                this.attack = saved.Attack;
                this.defense = saved.Defense;
                Console.WriteLine("读档结束:{0}", saved);
            }
        }
    
        /// 
        /// careTaker
        /// 
        public class 存取档工具
        {
            private System.Collections.Hashtable saves = new System.Collections.Hashtable();
    
            public void 存档(string 进度编号, 游戏进度 progress)
            {
                if (!this.saves.ContainsKey(进度编号))
                {
                    this.saves.Add(进度编号, progress);
                }
                else
                {
                    this.saves[进度编号] = progress;
                }
                Console.WriteLine("进度已保存,进度编号:{0},进度信息:{1}", 进度编号, progress);
            }
    
            public 游戏进度 读档(string 进度编号)
            {
                Console.WriteLine((this.saves[进度编号]).GetType());
                return this.saves[进度编号] as 游戏进度;
            }
        }
    
        internal class Program
        {
            static void Main(string[] args)
            {
                游戏 game = new 游戏();
                存取档工具 sltool = new 存取档工具();
                游戏进度 progress = game.存档();
    
                sltool.存档("000", progress);
                game.查看状态();  //查询初始状态
    
                Console.WriteLine("\n第一次战斗\n");
                game.战斗();  //第一次战斗
                game.查看状态();
                progress = game.存档();
                sltool.存档("001", progress);
    
                Console.WriteLine("\n第二次战斗\n");
                game.战斗();  //第二次战斗
                game.查看状态();
                progress = game.存档();
                sltool.存档("002", progress);
    
                Console.WriteLine("\n读档:000\n");
                progress = sltool.读档("000");
                game.读档(progress);
                game.查看状态();
    
                Console.WriteLine("\n读档:001\n");
                progress = sltool.读档("001");
                game.读档(progress);
                game.查看状态();
    
                Console.WriteLine("\n读档:002\n");
                progress = sltool.读档("002");
                game.读档(progress);
                game.查看状态();
    
                Console.ReadLine();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166

    实现结果

    在这里插入图片描述

    七:实现要点

    <1>让”游戏”自己来创建”进度”;
    <2>“存取档工具”只是负责”进度”的读取,并不关心”进度”的内部细节;
    <3>根据”进退”来重新初始化”游戏”内部细节的工作,应交给”游戏”自己实现;
    <4>“存取档工具”可维护多个”进度”。

    八:源码测试

    using System.Text;
    
    namespace Test
    {
        public class Memento
        {
            public int Life { get; set; }
    
            public override string ToString()
            {
                return String.Format("当前生命:{0}", this.Life);
            }
        }
    
        public interface IOriginator
        {
            void Load(Memento memento);
            Memento Save();
        }
    
        public class Game : IOriginator
        {
            private int life = 100;
    
            public void Fight()
            {
                System.Threading.Thread.Sleep(2000);
                this.life -= new Random().Next(100);
            }
    
            public override string ToString()
            {
                return String.Format("当前生命值:{0},游戏结束?{1}", this.life, this.life < 1);
            }
    
            public void Load(Memento memento)
            {
                this.life = memento.Life;
            }
    
            public Memento Save()
            {
                return new Memento()
                {
                    Life = this.life
                };
            }
        }
    
        public class CareTaker
        {
            private Dictionary<int, Memento> mementos = new Dictionary<int, Memento>();
    
            public void SaveMemento(int id, Memento memento)
            {
                this.mementos.Add(id, memento);
            }
    
            public Memento LoadMemento(int id)
            {
                return this.mementos[id];
            }
    
            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                foreach (var item in this.mementos)
                {
                    sb.AppendFormat("存档ID:{0}---{1}\n", item.Key, item.Value);
                }
                return sb.ToString();
            }
        }
    
        internal class Program
        {
            static void Main(string[] args)
            {
                IOriginator originator = new Game();
                Console.WriteLine(originator);
                Memento m = originator.Save();
                Console.WriteLine(m);
    
                CareTaker careTaker = new CareTaker();
                careTaker.SaveMemento(1, m);  //第一次存档
                (originator as Game).Fight();
                Console.WriteLine(originator);
    
                m = originator.Save();
                Console.WriteLine(m);
                careTaker.SaveMemento(2, m);  //第二次存档
                (originator as Game).Fight();
                Console.WriteLine(originator);
    
                m = originator.Save();
                Console.WriteLine(m);
                careTaker.SaveMemento(3, m);  //第三次存档
    
                Console.WriteLine("------------------");
                Console.WriteLine(careTaker);
    
                ///还原
                Console.WriteLine("------------------");
                Game game = originator as Game;
                game.Load(careTaker.LoadMemento(3));
                Console.WriteLine(game);
                game.Load(careTaker.LoadMemento(2));
                Console.WriteLine(game);
                game.Load(careTaker.LoadMemento(1));
                Console.WriteLine(game);
    
                Console.ReadLine();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    测试结果

    在这里插入图片描述

    九:适用性

    <1>必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态;
    <2>如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

    十:总结

    <1>备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态;
    <2>Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”;
    <3>在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器使用的宽接口;一个为其他对象使用的窄接口。
    <4>在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。

  • 相关阅读:
    负载均衡 - F5
    【无标题】
    基于蚁群算法的多配送中心的车辆调度问题的研究(Matlab代码实现)
    HTML5 用FileReader对象读取文件内容
    【02】Hadoop入门
    C++左值引用与右值引用
    短视频矩阵系统,抖音矩阵系统,抖音SEO源码。
    使用JPofiler工具分析OOM原因
    Java-IO流之字节输入流(中篇)
    Linux——iMX6ULL的启动过程详细解析(启动模式配置、启动设备配置、镜像烧写imx文件基本组成)
  • 原文地址:https://blog.csdn.net/zzyzxb/article/details/126749236