• golang设计模式——备忘录模式


    备忘录模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Th5IyjVl-1661237641221)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220822110925621.png)]

    当初学备忘录模式的时候,特别开心。这不就是游戏里的备份嘛!游戏关闭之后,重新开启,从上次结束的位置继续开始。但终归没有进入游戏行业,也没有机会用过备忘录模式。

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

    UML

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9DgdXwfq-1661237641223)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220822165546281.png)]

    分析

    从定义上看,除了不破坏封装性外,其它都比较容易理解。对于不破坏封装性,我觉得有两点:

    1. 备忘录是Originator自己创建的
    2. 备忘录应该只有获取相关的接口,不应该有修改相关的接口

    Caretaker是做什么用的呢?备忘录不是只备忘一份,可能备忘多份,Caretaker就是管理众多备忘录的。

    Originator通过CreateMemento创建备忘录,通过SetMemento恢复到指定状态。

    为什么备份的时候使用的是Memento而不是直接使用Originator呢?这是因为Memento只保存数据,如果将Originator保存,则表示将功能也进行保存,属于不该保存的而保存了。

    另外只保存数据还有一个好处,即使解析出数据,也不知道如何使用,只有Originator知道真正的口诀。

    应用场景

    游戏或者文档,经常使用到备份相关的功能。玩游戏打Boss的时候,一般会做存档,失败了重新来。文档也有回滚功能,否则毕业论文写着写着断电了,所有内容化为乌有,多惨。

    代码实现

    package main
    
    import (
       "container/list"
       "fmt"
    )
    
    /**
     * @Description: 备忘录
     */
    type Memento struct {
       mario *Mario
    }
    
    func (m *Memento) GetMario() *Mario {
       return m.mario
    }
    
    /**
     * @Description: 管理备忘录
     */
    type Caretaker struct {
       stack *list.List
    }
    
    /**
     * @Description: 保存备忘录
     * @receiver c
     * @param m
     */
    func (c *Caretaker) Save(m *Memento) {
       c.stack.PushBack(m)
    }
    
    /**
     * @Description: 获取上一个备忘录
     * @receiver c
     * @return *Memento
     */
    func (c *Caretaker) Pop() *Memento {
       e := c.stack.Back()
       c.stack.Remove(e)
       return e.Value.(*Memento)
    }
    
    type Mario struct {
       score  int64
       status MarioStatus
    }
    
    /**
     * @Description: 展示信息和分数
     * @receiver m
     */
    func (m *Mario) ShowInfo() {
       m.status.Name()
       fmt.Println("当前分数为:", m.score)
    }
    
    /**
     * @Description: 创建备忘录
     * @receiver m
     */
    func (m *Mario) CreateMemento() *Memento {
       return &Memento{
          mario: &Mario{
             score:  m.score,
             status: m.status,
          },
       }
    }
    
    /**
     * @Description: 恢复数据
     * @receiver m
     * @param mem
     */
    func (m *Mario) SetMemento(mem *Memento) {
       m.score = mem.mario.score
       m.status = mem.mario.status
    }
    
    type MarioStatus interface {
       Name()
       ObtainMushroom()
       ObtainCape()
       MeetMonster()
       SetMario(mario *Mario)
    }
    
    /**
     * @Description: 小马里奥
     */
    type SmallMarioStatus struct {
       mario *Mario
    }
    
    /**
     * @Description: 设置马里奥
     * @receiver s
     * @param mario
     */
    func (s *SmallMarioStatus) SetMario(mario *Mario) {
       s.mario = mario
    }
    
    func (s *SmallMarioStatus) Name() {
       fmt.Println("小马里奥")
    }
    
    /**
     * @Description: 获得蘑菇变为超级马里奥
     * @receiver s
     */
    func (s *SmallMarioStatus) ObtainMushroom() {
       s.mario.status = &SuperMarioStatus{
          mario: s.mario,
       }
       s.mario.score += 100
    }
    
    /**
     * @Description: 获得斗篷变为斗篷马里奥
     * @receiver s
     */
    func (s *SmallMarioStatus) ObtainCape() {
       s.mario.status = &CapeMarioStatus{
          mario: s.mario,
       }
       s.mario.score += 200
    }
    
    /**
     * @Description: 遇到怪兽减100
     * @receiver s
     */
    func (s *SmallMarioStatus) MeetMonster() {
       s.mario.score -= 100
    }
    
    /**
     * @Description: 超级马里奥
     */
    
    type SuperMarioStatus struct {
       mario *Mario
    }
    
    /**
     * @Description: 设置马里奥
     * @receiver s
     * @param mario
     */
    func (s *SuperMarioStatus) SetMario(mario *Mario) {
       s.mario = mario
    }
    
    func (s *SuperMarioStatus) Name() {
       fmt.Println("超级马里奥")
    }
    
    /**
     * @Description: 获得蘑菇无变化
     * @receiver s
     */
    func (s *SuperMarioStatus) ObtainMushroom() {
    
    }
    
    /**
     * @Description:获得斗篷变为斗篷马里奥
     * @receiver s
     */
    func (s *SuperMarioStatus) ObtainCape() {
       s.mario.status = &CapeMarioStatus{
          mario: s.mario,
       }
       s.mario.score += 200
    }
    
    /**
     * @Description: 遇到怪兽变为小马里奥
     * @receiver s
     */
    func (s *SuperMarioStatus) MeetMonster() {
       s.mario.status = &SmallMarioStatus{
          mario: s.mario,
       }
       s.mario.score -= 200
    }
    
    /**
     * @Description: 斗篷马里奥
     */
    type CapeMarioStatus struct {
       mario *Mario
    }
    
    /**
     * @Description: 设置马里奥
     * @receiver s
     * @param mario
     */
    func (c *CapeMarioStatus) SetMario(mario *Mario) {
       c.mario = mario
    }
    
    func (c *CapeMarioStatus) Name() {
       fmt.Println("斗篷马里奥")
    }
    
    /**
     * @Description:获得蘑菇无变化
     * @receiver c
     */
    func (c *CapeMarioStatus) ObtainMushroom() {
    
    }
    
    /**
     * @Description: 获得斗篷无变化
     * @receiver c
     */
    func (c *CapeMarioStatus) ObtainCape() {
    
    }
    
    /**
     * @Description: 遇到怪兽变为小马里奥
     * @receiver c
     */
    func (c *CapeMarioStatus) MeetMonster() {
       c.mario.status = &SmallMarioStatus{
          mario: c.mario,
       }
       c.mario.score -= 200
    }
    func main() {
       caretaker := &Caretaker{
          stack: list.New(),
       }
    
       mario := Mario{
          status: &SmallMarioStatus{},
          score:  0,
       }
       mario.status.SetMario(&mario)
    
       mario.status.Name()
       fmt.Println("-------------------获得蘑菇\n")
       mario.status.ObtainMushroom()
    
       mario.status.Name()
       fmt.Println("-------------------获得斗篷\n")
       mario.status.ObtainCape()
    
       fmt.Println("-------------------备份一下,要打怪了,当前状态为\n")
       mario.ShowInfo()
       caretaker.Save(mario.CreateMemento())
       fmt.Println("-------------------开始打怪\n")
    
       mario.status.Name()
       fmt.Println("-------------------遇到怪兽\n")
       mario.status.MeetMonster()
    
       fmt.Println("-------------------打怪失败,目前状态为\n")
       mario.ShowInfo()
    
       fmt.Println("-------------------恢复状态,重新打怪\n")
       mario.SetMemento(caretaker.Pop())
       mario.ShowInfo()
    }
    
    • 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
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272

    输出:

    ➜ myproject go run main.go

    小马里奥

    ——————-获得蘑菇

    超级马里奥

    ——————-获得斗篷

    ——————-备份一下,要打怪了,当前状态为

    斗篷马里奥

    当前分数为: 300

    ——————-开始打怪

    斗篷马里奥

    ——————-遇到怪兽

    ——————-打怪失败,目前状态为

    小马里奥

    当前分数为: 100

    ——————-恢复状态,重新打怪

    斗篷马里奥

    当前分数为: 300

    实例

    代码

    // Package memento 备忘录模式
    // 下面这个例子采用原课程的例子,一个输入程序
    // 如果输入 :list 则显示当前保存的内容
    // 如果输入 :undo 则删除上一次的输入
    // 如果输入其他的内容则追加保存
    package memento
    
    // InputText 用于保存数据
    type InputText struct {
    	content string
    }
    
    // Append 追加数据
    func (in *InputText) Append(content string) {
    	in.content += content
    }
    
    // GetText 获取数据
    func (in *InputText) GetText() string {
    	return in.content
    }
    
    // Snapshot 创建快照
    func (in *InputText) Snapshot() *Snapshot {
    	return &Snapshot{content: in.content}
    }
    
    // Restore 从快照中恢复
    func (in *InputText) Restore(s *Snapshot) {
    	in.content = s.GetText()
    }
    
    // Snapshot 快照,用于存储数据快照
    // 对于快照来说,只能不能被外部(不同包)修改,只能获取数据,满足封装的特性
    type Snapshot struct {
    	content string
    }
    
    // GetText GetText
    func (s *Snapshot) GetText() string {
    	return s.content
    }
    
    • 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

    单元测试

    package memento
    
    import (
    	"testing"
    
    	"github.com/stretchr/testify/assert"
    )
    
    func TestDemo(t *testing.T) {
    	in := &InputText{}
    	snapshots := []*Snapshot{}
    
    	tests := []struct {
    		input string
    		want  string
    	}{
    		{
    			input: ":list",
    			want:  "",
    		},
    		{
    			input: "hello",
    			want:  "",
    		},
    		{
    			input: ":list",
    			want:  "hello",
    		},
    		{
    			input: "world",
    			want:  "",
    		},
    		{
    			input: ":list",
    			want:  "helloworld",
    		},
    		{
    			input: ":undo",
    			want:  "",
    		},
    		{
    			input: ":list",
    			want:  "hello",
    		},
    	}
    	for _, tt := range tests {
    		t.Run(tt.input, func(t *testing.T) {
    			switch tt.input {
    			case ":list":
    				assert.Equal(t, tt.want, in.GetText())
    			case ":undo":
    				in.Restore(snapshots[len(snapshots)-1])
    				snapshots = snapshots[:len(snapshots)-1]
    			default:
    				snapshots = append(snapshots, in.Snapshot())
    				in.Append(tt.input)
    			}
    		})
    	}
    }
    
    • 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

    总结

    简单写了一个小功能,还是挺麻烦的,我想这也是大家不太想用设计模式的一个原因。但是当使用的时候,却发现这个设计模式能够方便的实现很多功能,这也是有人想用设计模式的原因。

    备忘录模式虽然不常用,但是对合适的场景还是很有帮助的。

  • 相关阅读:
    RMAN-08120的处理
    【线上问题】很抱歉,如果没有 JavaScript 支持,将不能正常工作
    java计算机毕业设计线上订餐系统MyBatis+系统+LW文档+源码+调试部署
    Windows上的多jdk版本管理工具
    【雷神笔记本快捷键】雷神笔记本FN功能快捷键大全以及电脑CPU处于低功耗但电脑风扇高速转动噪音较大解决方案
    sklearnex 让你的 sklearn 机器学习模型训练快得飞起?
    循环链表3
    MySQL权限与安全管理
    免费发布web APP的四个途径(Python和R)
    【JavaSE】JavaSE之控制逻辑
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126485125