Memento-Pattern(备忘录模式)

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

使用场景

使用Memento模式可以实现应用程序的以下功能

  • Undo(撤销)
  • Redo(重做)
  • History(历史记录)
  • Snapshot(快照)

    实现

    手机水果和获取金钱数的掷骰子游戏

    Memento类:表示主人公

    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
    /**
    * Memento类,主人公
    */
    export default class Memento {
    public money: number;//金钱
    public fruits: Array<string>;//水果
    /**
    * 获取当前金钱
    */
    public getMoney(): number {
    return this.money;
    }
    constructor(money: number) {
    this.money = money;
    this.fruits = new Array();
    }
    /**
    * 添加水果
    * @param fruit 水果
    */
    public addFruit(fruit: string) {
    this.fruits.push(fruit);
    }
    /**
    * 获取当前水果
    */
    public getFruits(): Array<string> {
    return JSON.parse(JSON.stringify(this.fruits));
    }
    }
    Gamer类:表示游戏主人公的类
    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
    import Memento from "./Memento";

    export default class Gamer {
    private money: number;//所持金钱
    private fruits: Array<string> = new Array();//获得的水果
    private fruitname: string[] = ["苹果", "葡萄", "香蕉", "橘子"];//水果种类
    constructor(money: number) {//构造函数
    this.money = money;
    }
    /**
    * 获取当前所持金钱
    */
    public getMoney(): number {
    return this.money;
    }
    /**
    * 掷骰子游戏
    */
    public bet(): void {
    const dice = Math.ceil(Math.random() * 6);//骰子随机数
    if (dice == 1) {//1金钱+100
    this.money += 100;
    console.log("所持金钱增加了。")
    } else if (dice == 2) {//2金钱减半
    this.money /= 2;
    console.log("所持金钱减半");
    } else if (dice == 6) {//6获取水果
    const f = this.getFruit();
    console.log(`获得了水果(${f})。`);
    this.fruits.push(f)
    } else {//其他没奖励
    console.log("什么都没有发生。")
    }
    }
    /**
    * 创建快照
    */
    public createMemento(): Memento {
    const m: Memento = new Memento(this.money);
    this.fruits.forEach((item) => {
    if (item.indexOf("好吃的") == 0) {//只保存还吃的水果
    m.addFruit(item)
    }
    })
    return m;
    }
    /**
    * 撤销操作
    * @param memento 快照
    */
    public restoreMemento(memento: Memento) {
    this.money = memento.money;
    this.fruits = memento.getFruits();
    }
    public toString(): string {
    return `[money = ${this.money}, fruits = ${JSON.stringify(this.fruits)}]`
    }
    /**
    * 获取水果,增加随机数,只有大于0.5才表示水果是好吃的
    */
    private getFruit(): string {
    let prefix = "";
    if (Math.random() >= 0.5) {
    prefix = "好吃的";
    }

    return `${prefix}${this.fruitname[Math.floor(Math.random() * this.fruitname.length)]} `
    }
    }
    #index.ts运行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import Gamer from "./Gamer";
    import Memento from "./Memento";

    const gamer: Gamer = new Gamer(100);
    let memento: Memento = gamer.createMemento();
    for (let i = 0; i < 100; i++) {
    console.log(`======${i}`);
    console.log(`当前状态:${gamer}`);
    gamer.bet();
    console.log(`所持金钱为${gamer.getMoney()}元`);
    console.log(`${gamer.getMoney()}元,${memento.getMoney()}`);
    if (gamer.getMoney() > memento.getMoney()) {
    console.log("所持金钱增加了许多,因此保存当前游戏状态");
    memento = gamer.createMemento();
    } else if (gamer.getMoney() < memento.getMoney() / 2) {
    console.log("所持金钱减少了许多,因此将游戏恢复至以前的状态");
    gamer.restoreMemento(memento);

    }
    }

    结果

    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
    ======0
    当前状态:[money = 100, fruits = []]
    所持金钱增加了。
    所持金钱为200元
    200元,100
    所持金钱增加了许多,因此保存当前游戏状态
    ======1
    当前状态:[money = 200, fruits = []]
    所持金钱减半
    所持金钱为100元
    100元,200
    ======2
    当前状态:[money = 100, fruits = []]
    所持金钱增加了。
    所持金钱为200元
    200元,200
    ======3
    当前状态:[money = 200, fruits = []]
    所持金钱减半
    所持金钱为100元
    100元,200
    ======4
    当前状态:[money = 100, fruits = []]
    什么都没有发生。
    所持金钱为100元
    100元,200
    ======5
    当前状态:[money = 100, fruits = []]
    什么都没有发生。
    所持金钱为100元
    100元,200
    ======6
    当前状态:[money = 100, fruits = []]
    什么都没有发生。
    所持金钱为100元
    100元,200
    ======7
    当前状态:[money = 100, fruits = []]
    所持金钱增加了。
    所持金钱为200元
    200元,200
    ======8
    当前状态:[money = 200, fruits = []]
    什么都没有发生。
    所持金钱为200元
    200元,200
    ======9
    当前状态:[money = 200, fruits = []]
    什么都没有发生。
    所持金钱为200元
    200元,200
    ##类图
    类图

结构

  • Originator(生成者)
    Originator角色保存自己的最新状态时生产Memento角色,当吧以前保存的Memento角色传递给Originator角色时,它会将自己恢复至生成该Memento角色时的状态。在示例中Gamer类扮演该角色
  • Memento(备忘录)
    Memento角色会将Originator角色的内部信息整合在一起,在Memento角色中虽然保存了Originator角色信息,但不会向外部公开这些信息
    Memento角色有以下两种接口(API)
  • wide interface---宽接口
    宽接口是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口会暴露所有信息,所以能使用宽接口的只有Originator角色
  • narrow interface ---窄接口
    可以通过窄接口获取的Memento的信息有限,有效放在信息泄露

示例中Memento类扮演该角色

  • Caretaker(负责人)
    当Caretaker角色想要保存当前Originator角色状态时,会通知Originator角色,Originator接到同事后会生成Memento角色的实例并返回给Caretaker角色。为了Originator状态恢复,Caretaker会一直保存Memento实例。
    示例中index扮演该角色