如果再回到从前--备忘录模式

1.1 如果再给我一次机会

"很多时候我们做了件事后,却又后悔。这就是人类内心软弱的一面。时间不能倒流,不管怎么样人生是无法回到从前的,但是软件就不一样了。还记得玩一些单机的PC游戏的时候吗,通常我都是在打大Boss之前,先保存一个进度,然后如果通关失败了,我可以再返回刚才那个进度来恢复原来的状态,从头来过。从这点上说,我们比姚明强。"

"哈,这其中的原理是不是就是把当前的游戏状态的各种参数存储,以便恢复时读取呢?"

"是的,通常这种保存都是存在磁盘上了,以便日后读取。但对于一些更为常规的应用,比如我们下棋时通常悔棋、编写文档时需要撤销、查看网页时需要后退,这些相对频繁而简单的恢复并不需要存在磁盘中,只要将保存在内存中的状态恢复一下即可。"

"嗯,这是更普通的应用,很多开发中都会用到。"

"那我简单说个场景,你想想看怎么用代码实现。游戏的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗前。"

1.2 游戏存进度

游戏角色类,用来存储角色的生命力、攻击力、防御力的数据。

package code.chapter18.memento1;

public class Test {
	
	public static void main(String[] args){

		System.out.println("**********************************************");		
		System.out.println("《大话设计模式》代码样例");
		System.out.println();		

        //大战Boss前
        GameRole role = new GameRole();
        role.getInitState();
        role.displayState();

        //保存进度
        GameRole backup = new GameRole();
        backup.setVitality(role.getVitality());
        backup.setAttack(role.getAttack());
        backup.setDefense(role.getDefense());

        //大战Boss时,损耗严重
        role.fight();
        //显示状态
        role.displayState();

        //游戏进度恢复
        role.setVitality(backup.getVitality());
        role.setAttack(backup.getAttack());
        role.setDefense(backup.getDefense());

        //显示状态
        role.displayState();

		System.out.println();
		System.out.println("**********************************************");

	}
}

//游戏角色类
class GameRole {
    //生命力
    private int vitality;
    public int getVitality(){
        return this.vitality;
    }
    public void setVitality(int value){
        this.vitality = value;
    }

    //攻击力
    private int attack;
    public int getAttack(){
        return this.attack;
    }
    public void setAttack(int value){
        this.attack = value;
    }

    //防御力
    private int defense;
    public int getDefense(){
        return this.defense;
    }
    public void setDefense(int value){
        this.defense = value;
    }

    //状态显示
    public void displayState(){
        System.out.println("角色当前状态:");
        System.out.println("体力:"+this.vitality);
        System.out.println("攻击力:"+this.attack);
        System.out.println("防御力:"+this.defense);
        System.out.println();
    }

    //获得初始状态(数据通常来自本机磁盘或远程数据接口)
    public void getInitState(){
        this.vitality = 100;
        this.attack = 100;
        this.defense = 100;
    }

    //战斗(在与Boss大战后游戏数据损耗为0)
    public void fight(){
        this.vitality = 0;
        this.attack = 0;
        this.defense = 0;
    }

}

"哈,你的经典理论,代码无错未必优。说吧,我有心理准备。"

"问题主要在于这客户端的调用。下面这一段有问题,因为这样写就把整个游戏角色的细节暴露给了客户端,你的客户端的职责就太大了,需要知道游戏角色的生命力、攻击力、防御力这些细节,还要对它进行'备份'。以后需要增加新的数据,例如增加'魔法力'或修改现有的某种力,例如'生命力'改为'经验值',这部分就一定要修改了。同样的道理也存在于恢复时的代码。"

"显然,我们希望的是把这些'游戏角色'的存取状态细节封装起来,而且最好是封装在外部的类当中。以体现职责分离。"

1.3 备忘录模式

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

备忘录模式(Memento)结构图

■ Originator(发起人): 负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator可根据需要决定Memento存储Originator的哪些内部状态。

■ Memento(备忘录): 负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。

■ Caretaker(管理者): 负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。

"就刚才的例子,'游戏角色'类其实就是一个Originator,而你用了同样的'游戏角色'实例'备份'来做备忘录,这在当需要保存全部信息时,是可以考虑的,而用clone的方式来实现Memento的状态保存可能是更好的办法,但是如果是这样的话,使得我们相当于对上层应用开放了Originator的全部(public)接口,这对于保存备份有时候是不合适的。"

"那如果我们不需要保存全部的信息以备使用时,怎么办?"

" 哈,对的,这或许是更多可能发生的情况,我们需要保存的并不是全部信息,而只是部分,那么就应该有一个独立的备忘录类Memento,它只拥有需要保存的信息的属性。"

1.4 备忘录模式基本代码

发起人(Originator)类:

备忘录(Memento)类:

管理者(Caretaker)类:

package code.chapter18.memento0;

public class Test {
	
	public static void main(String[] args){

		System.out.println("**********************************************");		
		System.out.println("《大话设计模式》代码样例");
		System.out.println();		

        //Originator初始状态,状态属性为"On"
        Originator o = new Originator();
        o.setState("On");
        o.show();

        Caretaker c = new Caretaker();
        //保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节
        c.setMemento(o.createMemento());

        //Originator改变了状态属性为"Off"
        o.setState("Off");
        o.show();

        //恢复原初始状态
        o.recoveryMemento(c.getMemento());
        o.show();

		System.out.println();
		System.out.println("**********************************************");
	}
}

//发起人类
class Originator {
    
    //状态
    private String state;
    public String getState(){
        return this.state;
    }
    public void setState(String value){
        this.state = value;
    }

    //显示数据
    public void show(){
        System.out.println("State:"+this.state);
    }

    //创建备忘录
    public Memento createMemento(){
        return new Memento(this.state);
    }

    //恢复备忘录
    public void recoveryMemento(Memento memento){
        this.setState(memento.getState());
    }

}

//备忘录类
class Memento {

    private String state;

    public Memento (String state){
        this.state = state;
    }
    
    public String getState(){
        return this.state;
    }
    public void setState(String value){
        this.state = value;
    }
}

//管理者
class Caretaker{

    private Memento memento;
    public Memento getMemento(){
        return this.memento;
    }
    public void setMemento(Memento value){
        this.memento = value;
    }
}

"哈,我明白了,这当中就是把要保存的细节给封装在了Memento中了,哪一天要更改保存的细节也不用影响客户端了。 那么这个备忘录模式都用在一些什么场合呢?"

"Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。"

"我记得好像命令模式也有实现类似撤销的作用?"

"哈,小子记性不错,如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态[DP]。 有时一些对象的内部信息必须保存在对象以外的地方,但是必须要由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来[DP], 从而可以恰当地保持封装的边界。"

"我感觉可能最大的作用还是在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原[DP] 这个作用吧?"

"把刚才的代码改成备忘录模式的。"

1.5 游戏进度备忘

package code.chapter18.memento2;

public class Test {
	
	public static void main(String[] args){

		System.out.println("**********************************************");		
		System.out.println("《大话设计模式》代码样例");
		System.out.println();		

        //大战Boss前
        GameRole role = new GameRole();
        role.getInitState();
        role.displayState();

        //保存进度
        RoleStateCaretaker stateAdmin = new RoleStateCaretaker();
        stateAdmin.setRoleStateMemento(role.saveState());

        //大战Boss时,损耗严重
        role.fight();
        //显示状态
        role.displayState();

        //游戏进度恢复
        role.recoveryState(stateAdmin.getRoleStateMemento());

        //显示状态
        role.displayState();

		System.out.println();
		System.out.println("**********************************************");

	}
}

//游戏角色类
class GameRole {
    //生命力
    private int vitality;
    public int getVitality(){
        return this.vitality;
    }
    public void setVitality(int value){
        this.vitality = value;
    }

    //攻击力
    private int attack;
    public int getAttack(){
        return this.attack;
    }
    public void setAttack(int value){
        this.attack = value;
    }

    //防御力
    private int defense;
    public int getDefense(){
        return this.defense;
    }
    public void setDefense(int value){
        this.defense = value;
    }

    //状态显示
    public void displayState(){
        System.out.println("角色当前状态:");
        System.out.println("体力:"+this.vitality);
        System.out.println("攻击力:"+this.attack);
        System.out.println("防御力:"+this.defense);
        System.out.println();
    }

    //获得初始状态(数据通常来自本机磁盘或远程数据接口)
    public void getInitState(){
        this.vitality = 100;
        this.attack = 100;
        this.defense = 100;
    }

    //战斗(在与Boss大战后游戏数据损耗为0)
    public void fight(){
        this.vitality = 0;
        this.attack = 0;
        this.defense = 0;
    }

    //保存角色状态
    public RoleStateMemento saveState(){
        return new RoleStateMemento(this.vitality,this.attack,this.defense);
    }

    //恢复角色状态
    public void recoveryState(RoleStateMemento memento){
        this.setVitality(memento.getVitality());
        this.setAttack(memento.getAttack());
        this.setDefense(memento.getDefense());
    }
}

//角色状态存储箱
class RoleStateMemento {
    private int vitality;
    private int attack;
    private int defense;

    //将生命力、攻击力、防御力存入状态存储箱对象中
    public RoleStateMemento (int vitality,int attack,int defense){
        this.vitality = vitality;
        this.attack = attack;
        this.defense = defense;
    }   
     
    //生命力
    public int getVitality(){
        return this.vitality;
    }
    public void setVitality(int value){
        this.vitality = value;
    }
    //攻击力
    public int getAttack(){
        return this.attack;
    }
    public void setAttack(int value){
        this.attack = value;
    }
    //防御力
    public int getDefense(){
        return this.defense;
    }
    public void setDefense(int value){
        this.defense = value;
    }
}

//角色状态管理者
class RoleStateCaretaker{

    private RoleStateMemento memento;
    public RoleStateMemento getRoleStateMemento(){
        return this.memento;
    }
    public void setRoleStateMemento(RoleStateMemento value){
        this.memento = value;
    }
}

"不错,写得还行。你要注意,备忘录模式也是有缺点的,角色状态需要完整存储到备忘录对象中,如果状态数据很大很多,那么在资源消耗上,备忘录对象会非常耗内存。"

"嗯,明白。所以也不是用得越多越好。"

"曾经有一个精彩的游戏摆在我的面前,但是我没有好好珍惜。等到死于Boss手下的时候才后悔莫及,尘世间最痛苦的事莫过于此。如果上天可以给我一个机会再来一次的话,我会对你说三个字,'存进度'。如果非要把这个进度加上一个保险,我希望是刻成光盘,流传万年!"

相关推荐
禁默31 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑41 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528744 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶44 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework1 小时前
【jenkins插件】
java
风_流沙1 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
乐之者v2 小时前
leetCode43.字符串相乘
java·数据结构·算法