• Memento模式(备忘录设计模式)


    Memento模式?

    使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。这个时候你需要使用Memento设计模式。(以及实例实现对状态的保存)

    • 关键字:
      1.·Undo(撤销)
      2.·Redo(重做)
      3.·History(历史记录)
      4。·Snapshot(快照)

    • 破坏封装性:
      将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护。

    • 宽窄接口

    1. wide interface——宽接口(APl)Memento角色提供的“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator角色。
    2. narrowinterface——窄接口(API)Memento角色为外部的Caretaker角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露。
      通过对外提供以上两种接口(API),可以有效地防止对象的封装性被破坏
    • 相关设计模式
      1.Command模式(第22章)在使用Command模式处理命令时,可以使用Memento模式实现撤销功能。
      2.Protype模式(第6章)在Memento模式中,为了能够实现快照和撤销功能,保存了对象当前的状态。保存的信息只是在恢复状态时所需要的那部分信息。
      而在Protype模式中,会生成一个与当前实例完全相同的另外一个实例。这两个实例的内容完全一样。
    1. State模式(第19章)在Memento模式中,是用“实例”表示状态。而在State模式中,则是用“类”表示状态。

    理清职责

    • 实现功能
    1. ·游戏是自动进行的
    2. ·游戏的主人公通过掷骰子来决定下一个状态
    3. ·当骰子点数为1的时候,主人公的金钱会增加·当骰子点数为2的时候,主人公的金钱会减少
    4. ·当骰子点数为6的时候,主人公会得到水果
    5. ·主人公没有钱时游戏就会结束

    >>>名字=>>>说明
    game |Memento|表示Gamer状态的类
    game |Gamer表示游戏主人公的类。它会生成Memento的实例进行游戏的类。它会事先保存Memento的实例,之后会根据需要恢复Gamer的状态
    null | MainT 这里为了方便起见使用MainT作为责任人保存用户状态

    UML

    时序图:

    Code

    • Gamer
    
    public class Gamer {
    
        /**
         * 下面的money 与 fruits 就是按照一般的定义方式去定义
         * 但是我们提取Memento的时候需要注意这个的获取规则
         */
        // 获得金钱
        private int money;
    
        // 获得的水果
        private List<String> fruits=new ArrayList<>();
    
        private Random random=new Random();
    
        private final static String[] fruitname=new String[]{
                "苹果","葡萄","香蕉","橘子"
        };
    
        public Gamer(int money) {
            this.money = money;
        }
    
        public int getMoney() {
            return money;
        }
    
        /**
         * 开始游戏
         * 骰子结果1,2 ,6进行不同的操作
         */
        public void bet(){
            int dice=random.nextInt(6)+1;
            if(dice==1){
                this.money+=100;
                System.out.println("金钱增加了!");
            }else if(dice==2){
                this.money/=2;
                System.out.println("金钱减半了!");
            }else if(dice==6){
                String f=getFruit();
                System.out.println("获得了水果["+f+"]!");
                this.fruits.add(f);
            }else{
                System.out.println("什么也不发生");
            }
        }
    
        /**
         * 快照方法
         */
        public Memento createMemento(){
            Memento memento = new Memento(this.money);
            Iterator<String> iterator = fruits.iterator();
    
            while (iterator.hasNext()){
                String s = iterator.next();
                if(s.startsWith("好吃的")){
                    memento.addFruit(s);
                }
            }
            return memento;
        }
    
        /**
         * 撤销方法
         */
        public void restoreMemento(Memento memento){
            this.money=memento.money;
            this.fruits=memento.fruits;
        }
    
    
    
        private String getFruit() {
            String prefix="";
            if(random.nextBoolean()){
                prefix="好吃的";
            }
            return prefix+fruitname[random.nextInt(fruitname.length)];
        }
    
        @Override
        public String toString() {
            return "Gamer{" +
                    "money=" + money +
                    ", fruits=" + fruits +
                    '}';
        }
    }
    
    
    
    • Memento
    
    public class Memento {
    
        /**
         * 使用过程中因为Memento与Gamer是强关联关系,但是又因为是在同一个game包下,
         * 使用可见性修饰符显得比较重要:
         * 这里的两个字段在同一个包下都是可以访问
         */
        int money;
        ArrayList<String> fruits;
    
        /**
         * 窄接口
         */
        public int getMoney(){
            return money;
        }
    
        /**
         * 这里是宽接口
         * @param money
         */
         Memento(int money) {
            this.money = money;
            this.fruits = new ArrayList<>();
        }
    
        /**
         * 这里是宽接口
         */
        void addFruit(String fruit){
             fruits.add(fruit);
        }
    
        /**
         * 这里是宽接口
         */
        ArrayList<String> getFruits(){
             return (ArrayList<String>) fruits.clone();
        }
    }
    
    
    
    
    
    
    
    
    • MainT
    public class MainT {
    
        /**
         * 这里的状态只是单个快照点,当你需要多个快照点的时候,
         * 单独创建一个snapshot类来管理,可以使用集合等,
         * 这里写个例子
         */
        public static void main(String[] args) {
    
            Gamer gamer = new Gamer(100);
    
            //保存的一个快照 初始状态
            Memento memento = gamer.createMemento();
    
            for (int i = 0; i < 100; i++) {
                System.out.println("===="+i);
                System.out.println("当前状态"+gamer);
    
                //开始游戏
                gamer.bet();
    
                System.out.println("还有多少钱"+gamer.getMoney()+"元");
    
                if(gamer.getMoney()>memento.getMoney()){
                    System.out.println("//保存新状态");
                    memento=gamer.createMemento();
                }else if(gamer.getMoney()<memento.getMoney()/2){
                    System.out.println("金钱减少一半了,恢复到原来的状态");
                    gamer.restoreMemento(memento);
                }
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
    
        }
    }
    
    
    public class SnapShotManger implements Serializable {
    
        private List<Memento> mementos=new ArrayList<>();
    
        /**
         * 实现java.io.Serializable接口
         * 用objectoutputstream的writeobject方法
         * 用objectInputStream的 readobject方法
         */
    
        /**
         * 保存
         */
    
        /**
         * 恢复
         */
    
    
    }
    
    
    
    
    
    
    
  • 相关阅读:
    php判断token有效期
    ecshop数据库操作
    CSS3选择器 :nth-child(n) 如何理解
    socket_wirte、socket_read、socket_send、socket_recv
    Mysql按指定顺序排序的两种写法
    Photoshop图片优化文件大小
    Wordpress中的自定义栏目是什么玩意,有什么用?
    Windows使用压缩包安装Mysql5.7.17服务
    Discuz论坛搬家后出现 Unknown column 'app1' in 'where clause' 原因及解决办法
    .PHP后缀大写导致Linux下Composer找不到类
  • 原文地址:https://www.cnblogs.com/dgwblog/p/9873969.html
Copyright © 2020-2023  润新知