4.5备忘录模式(5.6)

备忘录 (memento、记忆)模式满足系统对撤销/undo、回滚操作的需求
由于需要返回到变化前,原始对象Originator(原创者、真身)的发生变化的属性如state就必需被记忆。当state发生变化时,创建一个还原点(Restore Point),用一个备忘录/Memento对象记录state的当前状态。而当Originator的客户调用Originator.undo()时恢复到变化前的状态。

4.5.1 过程式实现

如果MyOriginator仅有一个状态int state需要记忆,例如state从100→80→60,然后undo到80→100,这是一个及其简单的工作,不需要太多类型参与,MyOriginator自己搞定。可以容易地给出过程式实现。下面的例程用于说明备忘录模式本身是一个简单的概念

只有一个状态int state需要记忆时,可以用数组int[] memento记录各次变化前的数据,数组下标标明当前数据。

例程4-10过程式备忘录
package property.memento;
import static tool.Print.*;
/**
 * @author yqj2065
 * @version 2014.9
 */
public class MyOriginator{
    private int state= 100;//要记忆的状态。可以有多个
    private int times = 0;//第几次
    private int[] memento=new int[6];//记忆6次
    public void modifyState(int x){
        memento[times++] =state;
        state = x;        
    }
    public void undo(){
        state=memento[--times] ;
    }
    @Override public String toString(){
        return ""+state;
    }
    public static void testMyOriginator(){
        MyOriginator o = new MyOriginator();pln(o);
        o.modifyState(80); pln("→"+o);
        o.modifyState(60); pln("→"+o);
        o.undo(); pln("undo→"+o);
        o.undo(); pln("undo→"+o);
    }
}
注意:如果一个 引用类型的状态State state需要记忆,而网上的一些例子保存的 不是State 而直接保存State 的各种成员变量,就不要看那些例程了——它们等价于过程式实现。

4.5.2 内部类 Vs. 不变对象

不失一般性,原始对象MyOriginator2有一个状态State state(有成员变量int x,y)需要记忆,每个备忘录/Memento对象记录一次State的快照,一系列的Memento对象交给备忘录的Caretaker/保管员管理。

设计Memento有一个基本要求:它保存的状态数据不得被修改。虽然MyOriginator2的state域不断在变化,但Memento对象封存了state的一个瞬间,因此将它设计为不变类(immutable class)。

如果备忘录单纯地满足系统对撤销/undo、回滚操作的需求,不考虑Originator的封装性,称为 白箱备忘录模式;考虑Originator的封装性,称为 黑箱备忘录模式

 

 

不管白猫黑猫,类图基本上如图所示。外界Test仅仅依赖于原始对象Originator,Originator提供设置还原点方法setRestorePoint()和回滚方法undo(),其他均被隐藏。GoF中没有说明Originator的用户角色,而Originator的接口值得商榷:外界需要工厂方法createMemento()吗?难道外界设置还原点时一定要指定Memento对象作为参数吗?在上面的代码中setRestorePoint()不需要指定Memento,因此工厂方法createMemento()也就不作为接口。



显然,Memento被保存的状态数据是不得被修改的。另一方面,Memento必须提供方法

State getState()

以便原始对象Originator获得State以前的某个状态从而实现反悔的目的。

如何在提供getState()方法的同时保证被保存的状态数据不被修改呢?有下列手段。

限制Originator之外的对象访问Memento.getState()方法。

如果Originator没有公开接口getState(),Memento提供的getState()方法是不负责任的——破坏封装性。为了保护Originator的封装性,可以使Memento对外界对象不可见,例如将Memento设计成Originator的内部私有类。最少也要将Memento.getState()方法的访问权限改为包级私有。[设计模式·5.6]中给备忘录模式的定义:『在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态』。 GoF的描述强调Originator的封装性。假如State是某个地点,外界通过访问多个Memento对象收集Originator的出没地,可能会找到规律来给Originator下套。

保证被保存的状态数据不被修改。在很多情况下,Originator公开接口getState(),并不在意外界访问它的State属性,此时备忘录模式 单纯地满足系统对撤销/undo、回滚操作的需求。

例程4-11不变类
package property.memento;
public final class Memento1{
    private final VirtualState state;//要记忆的状态,
    public Memento1(State state){
        this.state =new VirtualState(state) ;
    }
    State getState(){
        State state = new State(this.state.x,this.state.y);
        return state;
    }
    
    /**
     * 可以使用深克隆技术。
     */
    class VirtualState{
        private int x;
        private int y ; 
        public VirtualState(State state){
            x = state.getX();
            y = state.getY();
        }        
    }
}

注意:Memento1中的VirtualState意在提醒程序员,此时保存的状态不是Originator的成员state同一个对象。真正使用的Memento2对象是不变对象,即一经创建就不可改变的对象。

package property.memento;
<p>public final class Memento2{</p><p>    privatefinal State state;//要记忆的状态,</p><p>    publicMemento2(final State state){      </p><p>       this.state =new State(){{setX(state.getX());setY(state.getY());}};</p><p>    }</p><p>    StategetState(){</p><p>        returnnew State(){{setX(state.getX());setY(state.getY());}};</p><p>    }</p>}



Caretaker/保管员的作用是管理一系列Memento对象。可以采用 数组、Arraylist、HashMap或Stack保存一系列Memento对象,按照程序员选择的不同,设计合适的save(Memento2 m)和Memento2 getMem()方法。例如使用HashMap时,save方法需要一个key作为参数。

例程 7 4 保管员
package property.memento;
public class Caretaker2{
    private Memento2[] ms = new Memento2[6];
    private int times = 0;//第几次
    public Caretaker2(){    }
    public void save(Memento2 m){
        ms[times++] =m;
    }
    public Memento2 getMem(){
        return ms[--times];
    }
}
最后是原始对象MyOriginator2、State和测试代码。


例程 7 5 原始对象
package property.memento;
public class MyOriginator2{
    private int i;//不需要记忆的状态
    private State state = new State();//要记忆的状态。
    Caretaker2 c = new Caretaker2();
    public MyOriginator2(int i,int j){
        state.setX(i); 
        state.setY(j);
    }
    public void modifyState(int i,int j){
        c.save(new Memento2(state));
        state.setX(i);
        state.setY(j);
    }
    public void undo(){
        state=c.getMem().getState();
    }
    @Override public String toString(){
        return ""+state;
    }
}

package property.memento;
public class State{
    private int x,y ; 
    public State(){}
    public State(int i,int j){x = i;y = j;}
    public void setX(int i){x = i;}
    public void setY(int i){y = i;}
    public int getX(){        return x;    }
    public int getY(){        return y;    }
    @Override public String toString(){
        return "x="+x+" y="+y;
    }
}

    public static void testMyOriginator2(){
        MyOriginator2 o = new MyOriginator2(8,8); pln("      "+o);
        o.modifyState(5,7);pln("→    "+o);
        o.modifyState(2,3);pln("→    "+o);
        o.undo(); pln("undo→"+o);
        o.undo(); pln("undo→"+o);
    }
输出:

      x=8 y=8
→    x=5 y=7
→    x=2 y=3
undo→x=5 y=7
undo→x=8 y=8



最后编辑时间:2015.4.20


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值