前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >备忘录模式--游戏存档

备忘录模式--游戏存档

作者头像
zhanyd
发布2022-05-16 14:05:53
5060
发布2022-05-16 14:05:53
举报
文章被收录于专栏:编程我也会

引子

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

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

普通方法

小帅很快就写出了代码:

代码语言:javascript
复制
/**
 * 英雄类
 */
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;
    }
}
代码语言:javascript
复制
/**
 * 客户端类
 */
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);
    }
}

输出:

代码语言:javascript
复制
挑战boss之前的状态:
生命值:90
魔法值:85
攻击力:70

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

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

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

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

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

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

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

heroBackUp对象可能会被其他对象修改属性,这违背了封装原则。

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

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

备忘录模式

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

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

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

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

Originator类:

代码语言:javascript
复制
/**
 * 英雄类(就是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 :

代码语言:javascript
复制
/**
 * 备忘录类
 */
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:

代码语言:javascript
复制
/**
 * 负责人类
 */
public class Caretaker {

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

    public Memento getMemento() {
        return memento;
    }

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

}

Client :

代码语言:javascript
复制
/**
 * 客户端类
 */
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);
    }
}

输出:

代码语言:javascript
复制
挑战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对象中的属性状态。

优点

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

缺点

  • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程我也会 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引子
  • 普通方法
  • 备忘录模式
    • 撤销功能的实现
    • 总结
      • 优点
        • 缺点
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档