当初学备忘录模式的时候,特别开心。这不就是游戏里的备份嘛!游戏关闭之后,重新开启,从上次结束的位置继续开始。但终归没有进入游戏行业,也没有机会用过备忘录模式。
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
UML:
从定义上看,除了不破坏封装性外,其它都比较容易理解。对于不破坏封装性,我觉得有两点:
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()
}
输出:
➜ 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
}
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)
}
})
}
}
简单写了一个小功能,还是挺麻烦的,我想这也是大家不太想用设计模式的一个原因。但是当使用的时候,却发现这个设计模式能够方便的实现很多功能,这也是有人想用设计模式的原因。
备忘录模式虽然不常用,但是对合适的场景还是很有帮助的。