为了账号安全,请及时绑定邮箱和手机立即绑定

Head First设计模式读书总结——状态模式

标签:
Java 设计

策略模式和状态模式是双胞胎,在出生时才分开。你已经知道,策略模式是围绕可以互换的算法来创建成功业务的,然而,状态走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。
题例:万能糖果公司
我们认为糖果机的控制器需要如下图般的工作,希望你能用Java语言帮我们实现它,而且需要让设计能够尽量有弹性而且好维护,因为将来我们可能要为它增加更多的行为。
图片描述
状态机101
没错上图是一个状态图。
1:首先找出所有的状态:“没有25分钱”,“有25分钱”,“糖果售罄”,“售出糖果”。
2:接下来,创建一个实例变量来持有目前的状态,然后定义每个状态的值:

    final static int SOLD_OUT=0;
    final static int NO_QUARTER=1;
    final static int HAS_QUARTER=2;
    final static int SOLD=3;
    int state =SOLD_OUT;

3:现在,我们将所有系统中可以发生的动作整合起来:
“投入25分钱”,“退回25分钱”,“转动曲柄”,“发放糖果”
这些动作是糖果机的接口,这是你能对糖果机做的事情,
调用任何一个动作都会造成状态的转换,
发放糖果更多是糖果机的内部动作,机器自己调用自己。
4:现在,我们创建了一个类,它的作用就像是一个状态机,第每一个动作,我们都创建了一个对应的方法,这些方法利用条件语句来决定在每个状态内什么行为是恰当的。比如对“投入25分钱”这个动作来说,我们可以把对应方法写成下面的样子:

  public void insertQuarter(){
        if(state==HAS_QUARTER){

        }else if(state==SOLD_OUT){

        }else if(state ==SOLD){

        }else if(state==NO_QUARTER){

        }
    }

每一个可能的状态都需要用条件语句检查,然后对每一个可能的状态展现适当的行为。
写下代码

class GumballMachine{
    final static int SOLD_OUT=0;
    final static int NO_QUARTER=1;
    final static int HAS_QUARTER=2;
    final static int SOLD=3;
    int state =SOLD_OUT;
    int count =0;//存储糖果数量
    public  GumballMachine(int count){
        this.count=count;
        if(count>0){
            state=NO_QUARTER;
        }
    }
    //当有25分钱投入,就会执行这个方法
    public void insertQuarter(){
        if(state==HAS_QUARTER){
            System.out.println("如果已投入过25分钱,我们就告诉顾客");
        }else if(state==SOLD_OUT){
            state=HAS_QUARTER;
            System.out.println("如果是在“没有25分钱”的状态下,我们就接手25分钱," +
                    "并将状态展缓到“有25分钱”的状态");
        }else if(state ==SOLD){
            System.out.println("如果糖果已经售罄,我们就拒绝收钱");
        }else if(state==NO_QUARTER){
            System.out.println("如果顾客刚才买了糖果,就需要稍等一些,好让状态转换完毕。" +
                    "恢复到“没有25分钱”的状态");
        }
    }
    //先在,如果顾客试着退回25分钱就执行这个方法
    public void ejectQuarter(){
        if(state==HAS_QUARTER){
            System.out.println("如果有25分钱,我们就把钱退出来,回到“没有25分钱”的状态");
            state=NO_QUARTER;
        }else if(state==SOLD_OUT){
            System.out.println("如果没有25分钱的话,当然不能退出25分钱");
        }else if(state ==SOLD){
            System.out.println("顾客已经转动曲柄就不能再退钱了,他已经拿到糖果了");
        }else if(state==NO_QUARTER){
            System.out.println("如果糖果售罄,就不能接受25分钱,当然也不可能退钱");
        }
    }
    //顾客试着转动曲柄
    public void turnCrank(){
        if(state==HAS_QUARTER){
            System.out.println("别想骗过机器拿两次糖果");
        }else if(state==SOLD_OUT){
            System.out.println("我们需要先投入25分钱");
        }else if(state ==SOLD){
            System.out.println("我们不能给糖果,已经没有任何糖果了");
        }else if(state==NO_QUARTER){
            System.out.println("成功,他们拿到糖果了," +
                    "改变状态到“售出糖果”然后条用机器的disoense()方法");
            state=SOLD;
            dispense();
        }
    }
    //调用此方法,发放糖果
    public void dispense(){
        if(state==HAS_QUARTER){
            System.out.println("我们正在”出售糖果“状态,给他们糖果");
            count=count-1;
            /*
            我们在这里处理“糖果售罄的情况,如果这是最后一个糖果,将机器的状态设置到“糖果售罄””
               否则就回到“没有25分钱”的状态
             */
            if(count==0){
                System.out.println();
                state=SOLD_OUT;
            }else{
                state=NO_QUARTER;
            }
        }else if(state==SOLD_OUT){
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        }else if(state ==SOLD){
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        }else if(state==NO_QUARTER){
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        }
    }
}

该来的躲不掉……变更请求
已经把你的代码放进他们的机器中,到目前为止一切很顺利。现在需要把“购买糖果”编程一个游戏,这样可以大大增加我们的销售量。
当曲柄被转动时,有10%的几率掉下来的是两个糖果
混乱的状态
当你回顾上面的代码,并开始考虑如何修改它的时候
1:首先,你必须加上一个新的状态,称为“赢家”,这还不算太麻烦
2:然后,你必须在每个方法中加入一个新的条件判断来处理“赢家”状态,这可有你忙的。
3:特别是turnCrack()方法,会变的一团乱,因为你必须加上代码来检查目前的顾客是否是赢家,然后决定是切换到赢家状态还是售出糖果状态。
新的设计
不要维护我们现有的代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。
1:首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法。
2:然后为机器中的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为。
3:最后,我们要摆脱旧的条件代码,取而代之的方法是,将动作委托到状态类。
现在我们要把一个状态的所有行为放在一个类中,这么一来我们将行为局部化了,并使得事情更容易改变和理解。
定义状态接口和类
我们先完成第一个版本的糖果机的重新实现之后,再回来处理添加“赢家”的修改。
首先让我们创建一个State接口,所有的状态都必须实现这个接口:

interface State{
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

然后将设计中的每个状态都封装成一个类,每个都实现state接口:
实现我们的状态类:

class NoQuarterState implements State{

    GumballMachine gumballMachine;
    public NoQuarterState(GumballMachine gumballMachine){
        this.gumballMachine=gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("如果有人投入了25分钱,我们就打印出一条消息,说我们接受了25分钱," +
                "然后改变机器的状态到HasQuarterState");
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }

    @Override
    public void ejectQuarter() {
        System.out.println("如果没给钱,就不要求退钱");
    }

    @Override
    public void turnCrank() {
        System.out.println("如果没给钱,就不能要求糖果");
    }

    @Override
    public void dispense() {
        System.out.println("如果没得到钱,我们就不能发放糖果。");
    }
}

重新改造糖果机

class GumballMachine_ {
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;

    State state = soldState;
    int count = 0;

    public GumballMachine_(int count) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldOutState = new SoldState(this);
        this.count = count;
        if (count > 0) {
            state = noQuarterState;
        }
    }
    public void insertQuarter() {
        state.insertQuarter();
    }

    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank(){
        state.turnCrank();
        state.dispense();
    }
    void setState(State state){
        this.state=state;
    }
    void releaseBall(){
        System.out.println();
        if(count!=0){
            count=count-1;
        }
    }
    public State getSoldState() {
        return soldState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getSoldOutState() {
        return soldOutState;
    }
}

实现更多的状态
HasQuarterState(有25分钱)和SoldState(售出糖果)类……

class HasQuarterState implements State {
    GumballMachine_ gumballMachine;

    public HasQuarterState(GumballMachine_ gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("这是一个对此状态不恰当的动作");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("退出顾客的25分钱,并将状态转换到NoQuarterState状态");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    @Override
    public void turnCrank() {
        System.out.println("当曲柄转动,我们将状态转换到SoldState");
        gumballMachine.setState(gumballMachine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("这是次状态的另一个不恰当动作");
    }
}
class SoldState implements State {

    GumballMachine_ gumballMachine;

    public SoldState(GumballMachine_ gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("不恰当动作");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("不恰当动作");
    }

    @Override
    public void turnCrank() {
        System.out.println("不恰当动作");
    }

    @Override
    public void dispense() {
        gumballMachine.releaseBall();
        if(gumballMachine.getCount()>0){
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        }else{
           gumballMachine.setState(gumballMachine.getSoldState());
        }
    }
}

定义状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
状态模式类图
你会发现策略模式和这张类图根本就是一模一样。
但是这两个模式的差别在于它们的“意图”
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个,随着时间而流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变,但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
而策略模式而言,客户通常主动指定Context所要组合的策略对象时哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
一般的,我们把策略模式想成是除了继承之外的一种弹性替代方案,如果你使用继承定义了一个类的行为,你将被这个行为困住,是指要修改它都很难,有了策略模式,你可以通过组合不同的对象来改变行为。
我们把状态模式想成是不用咋icontext中防止旭东条件判断的替代方案,通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。
十次抽中一次的游戏,尚未解决……
首先要在GumballMahine类中加入一个状态:

class GumballMachine_ {
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;
    State winnerState;

    State state = soldState;
    int count = 0;
//其他方法
}
class WinnerState implements  State{
    GumballMachine_ gumballMachine;

    public WinnerState(GumballMachine_ gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("不恰当动作");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("不恰当动作");
    }

    @Override
    public void turnCrank() {
        System.out.println("不恰当动作");
    }

    @Override
    public void dispense() {
        //如果还有第二个糖果我们就把它释放出来。
        gumballMachine.releaseBall();
        if(gumballMachine.getCount()==0){
            gumballMachine.setState(gumballMachine.getSoldState());
        }else{
            gumballMachine.releaseBall();
            if(gumballMachine.getCount()>0){
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            }else{
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
}

完成这个游戏
我们需要实现机会随机数,还要增加一个进入WinnerState状态的转换,这两件事都要加进HasQuarterState,因为顾客会从这个状态中转动曲柄:

class HasQuarterState implements State {
    Random random = new Random(System.currentTimeMillis());
    GumballMachine_ gumballMachine;

    public HasQuarterState(GumballMachine_ gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("这是一个对此状态不恰当的动作");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("退出顾客的25分钱,并将状态转换到NoQuarterState状态");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    @Override
    public void turnCrank() {
        System.out.println("当曲柄转动,我们将状态转换到SoldState");
        int winner = random.nextInt(10);
        if ((winner == 0) && (gumballMachine.getCount() > 1)) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    @Override
    public void dispense() {
        System.out.println("这是次状态的另一个不恰当动作");
    }
}
点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
26
获赞与收藏
228

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消