通过改变对象内部状态帮助对象控制行为

以一个简单例子说明,假设我们要模拟制造一台糖果机器,对方给你的机器流程图如下
图片描述

ok,我们现在简单分析这张状态图,可将状态提取出来:有硬币,无硬币,售出糖果,糖果售空四个状态,行为动作提取出来:投入一个硬币,退回一个硬币,转动曲柄,发放糖果四个行为,当然还有一些特殊情况,具体情况参考代码

代码如下(参考)

GumballMachine

public class GumballMachine {
    /**
     * 状态模式 糖果机的状态都用一个不同整数表示
     * 
     */
    final static int SOLD_OUT = 0;// 糖果售空
    final static int NO_QUARTER = 1;// 没有投25分钱
    final static int HAS_QUARTER = 2;// 投了25分钱
    final static int SOLD = 3;// 糖果售出

    // 当前状态
    int state = SOLD_OUT;
    // 用来追踪糖果数量
    int count = 0;

    public GumballMachine(int count) {
        this.count = count;
        if (count > 0) {
            // 如果有糖果,机器变为没有投25分钱状态(待购买状态)
            state = NO_QUARTER;
        }
    }

    // 投入25分钱方法
    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("已经投过币了,不能再投了");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("请投入一枚硬币");
        } else if (state == SOLD_OUT) {
            System.out.println("不能再投币了,机器已经售空了");
        } else if (state == SOLD) {
            System.out.println("请稍等,正在为你出糖果");
        }
    }

    // 退出25分钱方法
    public void ejectQuarter() {
        // 1\当客户要退钱是
        if (state == HAS_QUARTER) {
            System.out.println("零钱退回");
            state = NO_QUARTER;// 进入没投币状态
        } else if (state == NO_QUARTER) {
            System.out.println("你没有投入硬币");
        } else if (state == SOLD) {
            System.out.println("对不起,你已经转动了曲轴,无法退币了");
        } else if (state == SOLD_OUT) {
            System.out.println("糖果售空,无法退币");
        }
    }

    // 转动曲轴方法(顾客)
    public void turnCrank() {
        if (state == HAS_QUARTER) {
            System.out.println("请转动曲轴");
            state = SOLD;// 状态变为售出状态
            dispense();// 调用发放糖果方法
        } else if (state == SOLD) {
            System.out.println("转动两次也不给你糖果");
        } else if (state == NO_QUARTER) {
            System.out.println("请先投硬币");
        } else if (state == SOLD_OUT) {
            System.out.println("机器售空");
        }

    }

    // 发放糖果方法
    private void dispense() {
        if (state == SOLD) {
            System.out.println("一包糖果出来了");
            count -= 1;
            // 当糖果数量0时候呢?
            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("没有糖果了");
        }
    }

    // 重写toString()方法输入糖果机信息
    @Override
    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("欢迎使用糖果机\n");
        result.append("糖果数量:" + count + "\n");
        result.append("当前糖果机状态:" + state);
        return result.toString();
    }
}

TestMain

public class TestMain {
    public static void main(String[] args) {
        // 装了5个糖
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine + "\n");// 打印糖果机信息

        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.turnCrank();// 转动曲柄        
        System.out.println(gumballMachine + "\n");// 打印糖果机信息

        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.ejectQuarter();// 要求退币
        gumballMachine.turnCrank();// 转动曲柄,拿不到糖果
        System.out.println(gumballMachine + "\n");// 打印糖果机信息

        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
        gumballMachine.ejectQuarter();// 要求机器退钱
        System.out.println(gumballMachine + "\n");// 打印糖果机信息

        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.insertQuarter();// 投入硬币
        gumballMachine.turnCrank();// 转动曲柄(拿到糖果)
        // 下面开始压力测试
        //gumballMachine.insertQuarter();// 投入硬币
        //gumballMachine.turnCrank();// 转动曲轴
        //gumballMachine.insertQuarter();// 投入硬币
        //gumballMachine.turnCrank();// 转动曲轴
        System.out.println(gumballMachine + "\n");// 打印糖果机信息
    }
}

效果图(无压力测试)
图片描述

效果图(压力测试)
图片描述

效果可以快速实现,但这样的代码显然还有不足之处,假设有个新的需求:增加一个幸运用户,就是购买者有10%的概率可以一次买到两颗糖果,这又要如何实现呢?先看流程图
图片描述

按照之前的1.0代码加新中奖者功能,显然不合适,要在每个方法里写入,显然太麻烦,这里就需要重构代码了。我们可以试着行为封装起来,也可以把糖果机器具体一下.我们要做的是如下:
1、首先定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法。
2、然后为机器中的每个状态实现类。这些类将负责在对应的状态下进行机器的行为。
3、重构旧代码,取而代之方式是将动作委托给状态类。

具体实现请看代码
结构图
图片描述

State

package Interface;

public interface State {

    /**
     * 将四种状态抽象出来成基类
     */

    // 投入硬币
    public void insertQuarter();

    // 退回硬币
    public void ejectQuarter();

    // 转动曲柄
    public void turnCrank();

    // 发放糖果
    public void dispense();
}

GumballMachine

package Machine;

public class GumballMachine {
    /**
     * 不在使用静态整数,都是用对象
     * 
     */
    State soldOutState;// 糖果售空
    State noQuarterState;// 没有投币
    State hasQuarterState;// 投了币
    State soldState;// 糖果售出
    State winnerState;// 中奖状态
    // 当前状态,持有的是(糖果售空)对象
    State state = soldOutState;
    int count = 0;

    // numberGumball构造器取得糖果的初始数目后,并把它存放在一个实例变量中
    public GumballMachine(int numberGumballs) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        winnerState = new WinnerState(this);

        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        }
    }

    /**
     * 机器的各个操作不在这里具体实现了 而是丢给接口,再让具体实现类去实现接口
     */
    // 添加硬币
    public void insertQuarter() {
        state.insertQuarter();
    }

    // 退出硬币
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    // 使用曲柄
    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

    // 变化状态
    public void setState(State state) {
        this.state = state;
    }

    // 糖果出货
    public void releaseBall() {
        System.out.println("一包糖果出来了");
        if (count != 0) {
            count = count - 1;
        }
    }

    public int getCount() {
        return count;
    }

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }

    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("欢迎使用糖果机\n");
        result.append("糖果数量:" + count + "\n");
        result.append("当前糖果机状态: " + state + "\n");
        return result.toString();
    }
}

各个行为类实现
HasQuarterState

package State_Implements;

public class HasQuarterState implements State {
    /**
     * 投币的实现类
     * 
     * @param gumballMachine
     */
    // 增加一个随机数产生器,10%机会
    Random randomWinner = new Random(System.currentTimeMillis());
    GumballMachine gumballMachine;

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

    public void insertQuarter() {
        System.out.println("你已经投过币了,不能再投了");
    }

    public void ejectQuarter() {
        System.out.println("硬币退出");
        // 退出硬币后,状态变为没有硬币(待购买)状态
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    public void turnCrank() {
        System.out.println("转动.....");
        int winner = randomWinner.nextInt(10);// 产生0-9随机数,当是0并且还有糖果的时候中奖了
        if ((winner == 0) && (gumballMachine.getCount() > 1)) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    public void dispense() {
        System.out.println("没有糖果出来");
    }

    public String toString() {
        return "等待使用曲柄";
    }
}

NoQuarterState

package State_Implements;
/**
 * 没有投币状态实现,
 * 都通通要实现状态基类
 * @author Joy
 *
 */
public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("你投了一个硬币");
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }
 
    public void ejectQuarter() {
        System.out.println("你没有投币,无法退币~~");
    }
 
    public void turnCrank() {
        System.out.println("你没有投币,无法继续~~");
     }
 
    public void dispense() {
        System.out.println("你需要投币才能买糖果");
    } 
 
    public String toString() {
        return "正在运营";
    }
}

SoldOutState

package State_Implements;

/**
 * 售空状态
 * 
 * @author Joy
 * 
 */
public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("抱歉,你不能在投币了,糖果售空了");

    }
 
    public void ejectQuarter() {
        System.out.println("抱歉,糖果售空,无法退币");
    }
 
    public void turnCrank() {
        System.out.println("抱歉,转动曲柄无效,糖果售空了");

    }
 
    public void dispense() {
        System.out.println("糖果售空了");
    }
 
    public String toString() {
        return "糖果售空";
    }
}

SoldState

package State_Implements;
/**
 * 卖出糖果状态
 * 
 * @author Joy
 * 
 */
public class SoldState implements State {
    GumballMachine gumballMachine;
 
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
       
    public void insertQuarter() {
        System.out.println("请稍等,糖果正出货");
    }
 
    public void ejectQuarter() {
        System.out.println("抱歉,你已使用曲柄,无法退币");
    }
 
    public void turnCrank() {
        System.out.println("曲柄不可重复使用");
    }
 
    public void dispense() {
        // 调用糖果出货方法
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            System.out.println("哎呀,糖果售空了");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
 
    public String toString() {
        return "一个糖果已出货";
    }
}

WinnerState

package State_Implements;
public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
    public void insertQuarter() {
        System.out.println("不能投币");
    }
 
    public void ejectQuarter() {
        System.out.println("不能投币");
    }
 
    public void turnCrank() {
        System.out.println("不能使用曲柄");
    }
 
    public void dispense() {
        System.out.println("恭喜中奖了,你得到两个糖果~~");
        gumballMachine.releaseBall();
        // 此时糖果机器里只有一颗时候,那么第二颗就出不来,状态变为售空状态
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() > 0) {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            } else {
                System.out.println("哎呀,糖果售空了");
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
 
    public String toString() {
        return "你是中奖者,得到两个糖果";
    }
}

GumballMachineTestDrive 测试类

package TestMain;

import Machine.GumballMachine;

public class GumballMachineTestDrive {

    public static void main(String[] args) {
        // 一开始5颗糖
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine + "\n");

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        System.out.println(gumballMachine + "\n");// 输出状态

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        System.out.println(gumballMachine + "\n");// 输出状态
    }
}

效果图
图片描述
图片描述
图片描述

状态模式定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
注:这个模式是将状态封装成为独立地类,并将动作委托给代表当前状态的对象。

状态模式类图如下,后来本人在回顾时发现状态模式和策略模式类图很相似,有兴趣朋友可以将两者去比较不同
图片描述

感谢你看到这里,状态模式到这里就结束了,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~~~很快我会发布下一个设计模式的内容,生命不息,编程不止!

参考书籍:《Head First 设计模式》

暖心先森
287 声望16 粉丝

生命不息,编程不止!