《Head First 设计模式》读书笔记(10)—— 状态模式 State

本文共3000词,阅读需要10分钟,本文相关代码地址被托管在 gitee

卷首语

策略模式和状态模式是双胞胎,在出生时才分开
策略模式围绕可以互换的算法来创建业务的,而状态是通过改变对象内部的状态来帮助对象控制自己的行为。

栗子

背景

万能糖果公司,他们对于糖果售卖机有着一张状态图

每个圆圈代表一个状态图,要注意首先不能做不符合当前条件的事情,第二不能做没有意义的事情,所以对每一个状态的改变要判断清楚才行

开始动手做——状态机101

大致思路:首先找出所有状态,然后对每一个状态用一个变量来表示,接着对系统中的各种动作组合起来,注意状态机需要合适的条件判断

 public 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 == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("如果是在“没有25分钱”的状态下,你应该投25分钱");
        } else if (state == SOLD_OUT) {
            System.out.println("如果糖果已经售罄,我们就拒绝收钱");
        } else if (state == SOLD) {
            System.out.println("如果顾客刚才买了糖果,就需要稍等一些,好让状态转换完毕,恢复到“没有25分钱”的状态");
        }
    }

    //先在,如果顾客试着退回25分钱就执行这个方法
    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("如果有25分钱,我们就把钱退出来,回到“没有25分钱”的状态");
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            System.out.println("如果没有25分钱的话,当然不能退出25分钱");
        } else if (state == SOLD) {
            System.out.println("顾客已经转动曲柄就不能再退钱了,他已经拿到糖果了");
        } else if (state == SOLD_OUT) {
            System.out.println("如果糖果售罄,就不能接受25分钱,当然也不可能退钱");
        }
    }

    //顾客试着转动曲柄
    public void turnCrank() {
        if (state == SOLD) {
            System.out.println("别想骗过机器拿两次糖果");
        } else if (state == NO_QUARTER) {
            System.out.println("我们需要先投入25分钱");
        } else if (state == SOLD_OUT) {
            System.out.println("我们不能给糖果,已经没有任何糖果了");
        } else if (state == HAS_QUARTER) {
            System.out.println("成功,他们拿到糖果了," + "改变状态到“售出糖果”然后条用机器的dispense()方法");
            state = SOLD;
            dispense();
        }
    }

    //调用此方法,发放糖果
    public void dispense() {
        if (state == SOLD) {
            System.out.println("我们正在”出售糖果“状态,给他们糖果");
            count = count - 1;
            /* 我们在这里处理“糖果售罄的情况,如果这是最后一个糖果,将机器的状态设置到“糖果售罄”” 否则就回到“没有25分钱”的状态 */
            if (count == 0) {
                System.out.println();
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        } else if (state == SOLD_OUT) {
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        } else if (state == HAS_QUARTER) {
            System.out.println("这些都不应该发生,但是如果做了,就得到错误提示");
        }
    }

    @Override
    public String toString() {
        return String.valueOf(this.count);
    }
}

上面对每一个动作都对要发生的状态进行条件判断,然后基本没什么问题

新背景:变点花样——十个人中有一个可以得到免费糖果

当转动曲柄的时候,有10%的概率得到两个糖果
突然整个世界都乱了,因为你貌似需要修改每个方法的代码
上面的代码了违反了开放关闭原则,而且没有封装变化,而且是针对实现

状态模式实现

具体代码可以看gitee里面,我觉得重点就是
1.将原来的0,1,2,3改为一个具体的状态类来实现,每个状态类都包含一个售卖机参数,为的是同步售卖机里面的数量
2.不要if语句,这个基本不能修改
3.对各种状态用类实现,如果有新状态可以直接使用新类,对扩展开放
4.将每个状态下的行为局部化自己的状态类中
5.客户绝不会调用某个状态,这些状态都被放在售卖机中进行调用

定义状态模式

状态模式允许对象在内部状态改变时改变他的行为,而对象看起来好像修改了他的类。
这个说的确实很玄乎,其实就是将状态封装成独立的类,并将动作委托给当前状态,每个状态都是那些动作,但是不同的状态下同一个方法也是不同的行为
反过来对于使用者,你只是一直在使用某些类的某个方法,但是你根本没看到类的改变甚至类的存在

状态模式类图

状态模式 vs 策略模式

状态模式和策略模式的类图很相近,相同点:
1.都是将类中的某个行为用接口实现,比如鸭子会各种飞会各种叫,那就写个fly接口,写个quack接口,然后实现各种情况的飞和叫,而状态也是将一个context可能出现的各种情况写个接口,在context里面具体决定状态的各种改变等等
2.利用组合的方式都很有弹性,策略下具体怎么飞可以直接由使用者决定,状态的代码量感觉多了许多,也是因为使用了组合,使用者甚至都感觉不到有这个类

但是,我觉得还是很容易区别开,状态模式主要解决的事在一个context下很多if判断,让用的人永远只和这个context交互,而策略模式是让用户可以更加弹性的决定这个context里面具体长什么样

重新解决十个要中奖一个的问题

现在我们只需要重新定义一个中类的状态类然后稍加修改几个逻辑就好了
具体还是参见gitee吧

整体来看,当遇到的问题可以归结到一个context上,而且还能有状态图,而且需要很多if判断的话,状态模式简直神器

发表评论

电子邮件地址不会被公开。