zoukankan      html  css  js  c++  java
  • 备忘录模式

    备忘录模式

    标签 : Java与设计模式


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

    (图片来源: 设计模式: 可复用面向对象软件的基础)
    将保存细节封装在Memento中, 后面即使改动了保存细节也不会影响客户端.


    模式实现

    案例: 游戏进度保存
    在攻击Boss前, 将当前游戏进度保存, 万一失败还可从保存点又一次開始.
    


    Originator-原发器

    • 负责创建一个备忘录Memento, 用以记录当前时刻它的内部状态(决定Memento存储哪些内部状态 -不一定是全部属性);
    • 可使用备忘录恢复内部状态.
    /**
     * 游戏角色, 原发器
     *
     * @author jifang
     * @since 16/8/29 上午10:05.
     */
    public class GameRoleOriginator {
    
        private int vit;    // 生命值
        private int atk;    // 攻击力
        private int def;    // 防御力
    
        public GameRoleOriginator(int vit, int atk, int def) {
            this.vit = vit;
            this.atk = atk;
            this.def = def;
        }
    
        public void fight() {
            vit -= 10;
            atk -= 8;
            def += 10;
        }
    
        public RoleStateMemento save() {
            return new RoleStateMemento(vit, atk, def);
        }
    
        public void recover(RoleStateMemento memento) {
            this.setVit(memento.getVit());
            this.setAtk(memento.getAtk());
            this.setDef(memento.getDef());
        }
    
        public int getVit() {
            return vit;
        }
    
        public void setVit(int vit) {
            this.vit = vit;
        }
    
        public int getAtk() {
            return atk;
        }
    
        public void setAtk(int atk) {
            this.atk = atk;
        }
    
        public int getDef() {
            return def;
        }
    
        public void setDef(int def) {
            this.def = def;
        }
    
        @Override
        public String toString() {
            return "GameRoleOriginator{" +
                    "vit=" + vit +
                    ", atk=" + atk +
                    ", def=" + def +
                    '}';
        }
    }

    Memento-备忘录

    • 负责存储Originator的内部状态(与Originator共同决定存储原发器哪些内部状态);
    • 防止Originator以外对象訪问备忘录. Memento实际应该有两个接口: Caretaker仅仅能看到一个窄接口 -仅仅能将备忘录传递给其它对象. Originator能够看到一个宽接口, 同意它訪问返回先前状态所需的全部数据(C++中可由friend提供支持). 理想的情况是仅仅同意生成本备忘录的那个原发器訪问本备忘录的内部状态.
    public class RoleStateMemento {
    
        private int vit;
        private int atk;
        private int def;
    
        public RoleStateMemento(int vit, int atk, int def) {
            this.vit = vit;
            this.atk = atk;
            this.def = def;
        }
    
        public int getVit() {
            return vit;
        }
    
        public void setVit(int vit) {
            this.vit = vit;
        }
    
        public int getAtk() {
            return atk;
        }
    
        public void setAtk(int atk) {
            this.atk = atk;
        }
    
        public int getDef() {
            return def;
        }
    
        public void setDef(int def) {
            this.def = def;
        }
    }

    Caretaker-负责人

    • 负责保存好备忘录Memento;
    • 不能对备忘录内容进行操作或检查.
    public class RoleStateCaretaker {
    
        private Deque<RoleStateMemento> stack = new LinkedList<>();
    
        public void save(RoleStateMemento memento) {
            stack.push(memento);
        }
    
        public RoleStateMemento checkout() {
            return stack.pop();
        }
    }
    • Client
    public class Client {
    
        @Test
        public void client() {
            RoleStateCaretaker caretaker = new RoleStateCaretaker();
    
            GameRoleOriginator originator = new GameRoleOriginator(100, 50, 50);
            System.out.println("角色初始状态: " + originator);
    
            // 保存进度
            caretaker.save(originator.save());
    
            System.out.println("fight boss...");
            originator.fight();
            System.out.println("阻击Boss后的状态: " + originator);
    
            originator.recover(caretaker.checkout());
            System.out.println("恢复后的状态: " + originator);
        }
    }

    序列化全部属性

    假设Memento须要保存的是Originator的全部属性, 那么可将Originator的全部属性都存储到一个Map<String, Object>结构中由Caretaker保存, 这样就节省了Memento中间类的开发成本. 甚至还可将Originator序列化为二进制流/字符串存储到持久化设备中(如磁盘、DB、Redis), 节省内存开销, 以下演示将Originator转化为Map<String, Object>存储:

    • Originator
      注意save()/recover()的变化:
    public class GameRoleOriginator {
    
        private int vit;    // 生命值
        private int atk;    // 攻击力
        private int def;    // 防御力
    
        // ...
    
        public void fight() {
            vit -= 10;
            atk -= 8;
            def += 10;
        }
    
        public Map<String, Object> save() {
            try {
                return BeanUtil.bean2Map(this);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    
    
        public void recover(Map<String, Object> memento) {
            GameRoleOriginator bean;
            try {
                bean = BeanUtil.map2Bean(memento);
            } catch (IllegalAccessException | InstantiationException | NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
            this.setVit(bean.getVit());
            this.setAtk(bean.getAtk());
            this.setDef(bean.getDef());
        }
    
        // ...
    }
    • BeanUtil
    public class BeanUtil {
    
        public static Map<String, Object> bean2Map(Object object) throws IllegalAccessException {
            Map<String, Object> map = new HashMap<>();
            Class<?> clazz = object.getClass();
            map.put("class", clazz);
    
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String key = field.getName();
                Object value = field.get(object);
                map.put(key, value);
            }
    
            return map;
        }
    
        public static <T> T map2Bean(Map<String, Object> map) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
            Class<?> clazz = (Class<?>) map.get("class");
            Field[] fields = clazz.getDeclaredFields();
            Object object = clazz.newInstance();
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = map.get(field.getName());
                field.set(object, value);
            }
    
            return (T) object;
        }
    }

    小结

    • 适用性

      • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后须要时它才干恢复到先前的状态;
      • 假设一个用接口来让其它对象直接得到这些状态, 将会暴露对象的实现细节并破坏对象的封装性, 而Memento能够把复杂的对象内部信息对其它的对象屏蔽起来, 从而能够恰当的保持封装的边界.
        • 事务回滚;
        • 棋类游戏中的悔棋;
        • PhotoShop的历史记录.
    • 相关模式

      • 命令模式: 假设在使用命令模式时须要实现命令的撤销, 那么可用Memento来存储可撤销的状态.
      • 迭代器模式: 备忘录可用于迭代.

    參考:
    设计模式: 可复用面向对象软件的基础
    大话设计模式
    高淇讲设计模式
    《JAVA与模式》之备忘录模式
    23种设计模式(15):备忘录模式


  • 相关阅读:
    2014第16周六
    2014第16周五
    2014第16周四
    2014第16周三CSS布局再学习摘录
    2014第16周二
    2014第16周一
    2014第15周日
    2014第15周六
    2014第15周五
    SDN:软件定义网络
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8456269.html
Copyright © 2011-2022 走看看