备忘录模式--游戏存档

引子

小帅在一家游戏公司工作。最近领导让他设计游戏的自动存档功能,每次玩家要挑战boss的时候,系统要能够实现自动存档。如果玩家挑战失败,game over 了,还能恢复到挑战之前的状态再来一次。

小帅心想,我用一个备份对象把所有的游戏参数记录下来,当玩家要读取存档的时候,再把备份对象里的数据恢复回来不就行了吗?

普通方法

小帅很快就写出了代码:

/**
 * 英雄类
 */
public class Hero {
    
    

    /**
     * 生命值
     */
    private int healthPoint;

    /**
     * 魔法值
     */
    private int magicalValue;

    /**
     * 攻击力
     */
    private int attackPower;

    public Hero(int healthPoint, int magicalValue, int attackPower) {
    
    
        this.healthPoint = healthPoint;
        this.magicalValue = magicalValue;
        this.attackPower = attackPower;
    }

    /**
     * 游戏结束
     */
    public void gameOver() {
    
    
        this.healthPoint = 0;
        this.magicalValue = 0;
        this.attackPower = 0;
    }

    /**
     * 设置属性
     * @param healthPoint
     * @param magicalValue
     * @param attackPower
     */
    public void setState(int healthPoint, int magicalValue, int attackPower) {
    
    
        this.healthPoint = healthPoint;
        this.magicalValue = magicalValue;
        this.attackPower = attackPower;
    }


    @Override
    public String toString() {
    
    
        StringBuffer display = new StringBuffer();
        display.append("生命值:" + this.healthPoint + "\n");
        display.append("魔法值:" + this.magicalValue + "\n");
        display.append("攻击力:" + this.attackPower + "\n");
        return display.toString();
    }

    public int getHealthPoint() {
    
    
        return healthPoint;
    }

    public void setHealthPoint(int healthPoint) {
    
    
        this.healthPoint = healthPoint;
    }

    public int getMagicalValue() {
    
    
        return magicalValue;
    }

    public void setMagicalValue(int magicalValue) {
    
    
        this.magicalValue = magicalValue;
    }

    public int getAttackPower() {
    
    
        return attackPower;
    }

    public void setAttackPower(int attackPower) {
    
    
        this.attackPower = attackPower;
    }
}
/**
 * 客户端类
 */
public class Client {
    
    

    public static void main(String[] args) {
    
    
        Hero hero = new Hero(90,85,70);
        // 挑战boss之前的状态
        System.out.println("挑战boss之前的状态:\n" + hero);
        // 保存进度
        Hero heroBackUp = new Hero(hero.getHealthPoint(), hero.getMagicalValue(), hero.getAttackPower());
        // 挑战失败
        hero.gameOver();
        System.out.println("挑战失败后的状态:\n" + hero);
        // 恢复进度
        hero.setState(heroBackUp.getHealthPoint(), heroBackUp.getMagicalValue(), heroBackUp.getAttackPower());
        System.out.println("恢复进度后的状态:\n" + hero);
    }
}

输出:

挑战boss之前的状态:
生命值:90
魔法值:85
攻击力:70

挑战失败后的状态:
生命值:0
魔法值:0
攻击力:0

恢复进度后的状态:
生命值:90
魔法值:85
攻击力:70

小帅觉得这不是很简单吗?

我只要再建个heroBackUp对象,把hero对象的状态保存进去,等需要读档的时候再读取heroBackUp对象中的状态不就行了吗?

这时候项目组里的老王发话了:

你直接用Hero类的对象作为备份,方便是方便,但是不安全,Hero类中有属性公有的set方法。备份的heroBackUp对象,有可能会被别人调用set方法更改属性。

备份对象是只读的,不能被修改。

在这里插入图片描述
heroBackUp对象可能会被其他对象修改属性,这违背了封装原则。

那如何才能在不违背封装原则的前提下,备份一个对象呢?小帅连忙问道。

老王微微一笑:这就要用到备忘录模式了。

备忘录模式

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

备忘录模式是一种行为设计模式, 允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

在这里插入图片描述

  • Memento(备忘录):备忘录存储原发器对象的内部状态;备忘录的内部状态只能由原发器访问。
  • Originator(原发器):创建备忘录,记录当前的状态;使用备忘录恢复状态。
  • Caretaker(负责人):管理备忘录;不能对备忘录的内容进行操作或检查。

老王没多久就把代码改造好了:

Originator类:

/**
 * 英雄类(就是Originator)
 */
public class Hero {
    
    

    /**
     * 生命值
     */
    private int healthPoint;

    /**
     * 魔法值
     */
    private int magicalValue;

    /**
     * 攻击力
     */
    private int attackPower;

    public Hero(int healthPoint, int magicalValue, int attackPower) {
    
    
        this.healthPoint = healthPoint;
        this.magicalValue = magicalValue;
        this.attackPower = attackPower;
    }

    /**
     * 游戏结束
     */
    public void gameOver() {
    
    
        this.healthPoint = 0;
        this.magicalValue = 0;
        this.attackPower = 0;
    }


    /**
     * 创建备忘录
     * @return
     */
    public Memento createMemento() {
    
    
        return new Memento(healthPoint, magicalValue, attackPower);
    }

    /**
     * 从备忘录中恢复数据
     * @param memento
     */
    public void restoreMemento(Memento memento) {
    
    
        this.healthPoint = memento.getHealthPoint();
        this.magicalValue = memento.getMagicalValue();
        this.attackPower = memento.getAttackPower();
    }

    @Override
    public String toString() {
    
    
        StringBuffer display = new StringBuffer();
        display.append("生命值:" + this.healthPoint + "\n");
        display.append("魔法值:" + this.magicalValue + "\n");
        display.append("攻击力:" + this.attackPower + "\n");
        return display.toString();
    }
}

Memento :

/**
 * 备忘录类
 */
public class Memento {
    
    

    /**
     * 生命值
     */
    private int healthPoint;

    /**
     * 魔法值
     */
    private int magicalValue;

    /**
     * 攻击力
     */
    private int attackPower;


    public Memento(int healthPoint, int magicalValue, int attackPower) {
    
    
        this.healthPoint = healthPoint;
        this.magicalValue = magicalValue;
        this.attackPower = attackPower;
    }

    /**
     * 备忘录中只有get方法,没有set方法,因为备忘录中的数据不应该被修改
     * @return
     */
    public int getHealthPoint() {
    
    
        return healthPoint;
    }

    /**
     * 备忘录中只有get方法,没有set方法,因为备忘录中的数据不应该被修改
     * @return
     */
    public int getMagicalValue() {
    
    
        return magicalValue;
    }

    /**
     * 备忘录中只有get方法,没有set方法,因为备忘录中的数据不应该被修改
     * @return
     */
    public int getAttackPower() {
    
    
        return attackPower;
    }

}

Caretaker:

/**
 * 负责人类
 */
public class Caretaker {
    
    

    /**
     * 备忘录
     */
    private Memento memento;

    public Memento getMemento() {
    
    
        return memento;
    }

    public void setMemento(Memento memento) {
    
    
        this.memento = memento;
    }

}

Client :

/**
 * 客户端类
 */
public class Client {
    
    
    public static void main(String[] args) {
    
    
        Hero hero = new Hero(90,85,70);
        // 挑战boss之前的状态
        System.out.println("挑战boss之前的状态:\n" + hero);
        // 保存进度
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(hero.createMemento());
        // 挑战失败
        hero.gameOver();
        System.out.println("挑战失败后的状态:\n" + hero);
        // 恢复进度
        hero.restoreMemento(caretaker.getMemento());
        System.out.println("恢复进度后的状态:\n" + hero);
    }
}

输出:

挑战boss之前的状态:
生命值:90
魔法值:85
攻击力:70

挑战失败后的状态:
生命值:0
魔法值:0
攻击力:0

恢复进度后的状态:
生命值:90
魔法值:85
攻击力:70

我定义一个独立的类(Memento 类)来表示备份,而不是复用 Hero类。这个类只暴露 get() 方法,没有 set() 等任何修改内部状态的方法。这样就保证了数据不会被修改,符合了封装原则。

Caretaker类专门负责管理Memento类,但是Caretaker类对Memento类的权限有限,不能修改Memento类的数据。

老王总结道。

撤销功能的实现

老王接着说:如果我们想实现常见的撤销功能,可以在Caretaker类中用Stack来存储Memento对象。

每进行一次操作就调用Stack的push()方法把一个Memento对象入栈。

撤销的时候就调用Stack的pop()方法出栈,取出一个Memento对象,来恢复状态。

小帅听完不禁赞叹:你可比我家隔壁的老王厉害多了!

总结

当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。

该模式建议将对象状态的副本存储在一个名为备忘录 (Memento) 的特殊对象中。 备忘录让对象自行负责创建其状态的快照,除了创建备忘录的对象外, 任何其他对象都不能访问备忘录的内容。

Caretaker对象必须使用受限接口与备忘录进行交互, 它可以获Memento对象本身, 但它不能获取和更改Memento对象中的属性状态。

优点

  • 可以在保持封装状态的情况下,创建对象状态的备忘录,保证了备忘录数据的安全。
  • 可以通过让负责人维护备忘录,来简化原发器代码。

缺点

  • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。

代码链接

猜你喜欢

转载自blog.csdn.net/zhanyd/article/details/120056504