Head First 设计模式(十)状态模式

定义

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类

状态模式和前面学习的策略模式很相似,也是将类的“状态”封装了起来,在执行动作时进行自动的转换,从而实现,类在不同状态下的同一动作显示出不同结果。它与策略模式的区别在于,这种转换是“自动”,“无意识”的。

状态模式的类图如下:

状态模式的类图与策略模式一模一样,区别在于它们的意图。策略模式会控制对象使用什么策略,而状态模式会自动改变状态。看完下面的案例应该就清楚了。

场景+代码

场景

现在有一个糖果机的需求摆在你面前,需要用Java实现。

我们分析一下,糖果机的功能可以分为下图所示的四个动作和四个状态:

在不同状态下,同样的动作结果不一样。例如,在“投了25分钱”的状态下“转动曲柄”,会售出糖果;而在“没有25分钱”的状态下“转动曲柄”会提示请先投币。

代码一

简单思考后,我们写出如下的糖果机实现代码:

package demo.state;

/**
 * 没有采用设计模式的糖果机
 */
public class NoPatternGumballMachine{
    /*
     * 四个状态
     */
    /**没有硬币状态*/
    private final static int NO_QUARTER = 0;
    /**投币状态*/
    private final static int HAS_QUARTER = 1;
    /**出售糖果状态*/
    private final static int SOLD = 2;
    /**糖果售尽状态*/
    private final static int SOLD_OUT = 3;

    private int state = SOLD_OUT;
    private int candyCount = 0;

    public NoPatternGumballMachine(int count) {
        this.candyCount = count;
        if(candyCount > 0)
            state = NO_QUARTER;
    }

    /*
     * 四个动作
     */

    /**
     * 投币
     */
    public void insertQuarter() {
        if(NO_QUARTER == state){
            System.out.println("投币");
            state = HAS_QUARTER;
        }
        else if(HAS_QUARTER == state){
            System.out.println("请不要重复投币!");
            returnQuarter();
        }
        else if(SOLD == state){
            System.out.println("已投币,请等待糖果");
            returnQuarter();
        }else if(SOLD_OUT == state){
            System.out.println("糖果已经售尽");
            returnQuarter();
        }
    }

    /**
     * 退币
     */
    public void ejectQuarter() {
        if(NO_QUARTER == state){
            System.out.println("没有硬币,无法弹出");
        }
        else if(HAS_QUARTER == state){
            returnQuarter();
            state = NO_QUARTER;
        }
        else if(SOLD == state){
            System.out.println("无法退币,正在发放糖果,请等待");
        }else if(SOLD_OUT == state){
            System.out.println("没有投币,无法退币");
        }
    }

    /**
     * 转动出糖曲轴
     */
    public void turnCrank() {
        if(NO_QUARTER == state){
            System.out.println("请先投币");
        }
        else if(HAS_QUARTER == state){
            System.out.println("转动曲轴,准备发糖");
            state = SOLD;
        }
        else if(SOLD == state){
            System.out.println("已按过曲轴,请等待");
        }else if(SOLD_OUT == state){
            System.out.println("糖果已经售尽");
        }
    }

    /**
     * 发糖
     */
    public void dispense() {
        if(NO_QUARTER == state){
            System.out.println("没有投币,无法发放糖果");
        }
        else if(HAS_QUARTER == state){
            System.out.println("this method don't support");
        }
        else if(SOLD == state){
            if(candyCount > 0){
                System.out.println("分发一颗糖果");
                candyCount --;
                state = NO_QUARTER;
            }
            else{
                System.out.println("抱歉,糖果已售尽");
                state = SOLD_OUT;
            }
        }else if(SOLD_OUT == state){
            System.out.println("抱歉,糖果已售尽");
        }
    }

    /**
     * 退还硬币
     */
    protected void returnQuarter() {
        System.out.println("退币……");
    }

}

从代码里面可以看出,糖果机根据此刻不同的状态,而使对应的动作呈现不同的结果。这份代码已经可以满足我们的基本需求,但稍微思考一下,你会觉得这种实现代码似乎,功能太复杂了,扩展性很差,没有面向对象的风格。

假设由于新需求,要增加一种状态,那每个动作方法我们都需要修改,都要重新增加一条else语句。而如果需求变更,某个状态下的动作需要修改,我们也要同时改动四个方法。这样的工作将是繁琐而头大的。

代码二

怎么办,我们回忆到策略模式中学到的设计原则:

找出应用中可能需要变化之处,把他们独立出来。

在糖果机中,状态就是一直在变化的部分,不同的状态动作不一样。我们完全可以将其抽离出来:

新的设计想法如下:

  1. 首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个对应的方法
  2. 然后为机器的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为
  3. 最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类

直接上代码吧,代码最清晰

  • 定义一个State接口
public abstract class State {
    /**
     * 投币
     */
    public abstract void insertQuarter();

    /**
     * 退币
     */
    public abstract void ejectQuarter();

    /**
     * 转动出糖曲轴
     */
    public abstract void turnCrank();

    /**
     * 发糖
     */
    public abstract void dispense();

    /**
     * 退还硬币
     */
    protected void returnQuarter() {
        System.out.println("退币……");
    }
}
  • 为机器的每个状态实现状态类
/**
 * 没有硬币的状态
 */
public class NoQuarterState extends State{
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("你投入了一个硬币");
        //转换为有硬币状态
        gumballMachine.setState(gumballMachine.hasQuarterState);
    }

    @Override
    public void ejectQuarter() {
        System.out.println("没有硬币,无法弹出");
    }

    @Override
    public void turnCrank() {
        System.out.println("请先投币");
    }

    @Override
    public void dispense() {
        System.out.println("没有投币,无法发放糖果");
    }

}

/**
 * 投硬币的状态
 */
public class HasQuarterState extends State{
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("请不要重复投币!");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        returnQuarter();
        gumballMachine.setState(gumballMachine.noQuarterState);
    }

    @Override
    public void turnCrank() {
        System.out.println("转动曲轴,准备发糖");
        gumballMachine.setState(gumballMachine.soldState);
    }

    @Override
    public void dispense() {
        System.out.println("this method don't support");
    }

}

/**
 * 出售的状态
 */
public class SoldState extends State{
    GumballMachine gumballMachine;

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

    @Override
    public void insertQuarter() {
        System.out.println("已投币,请等待糖果");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println("无法退币,正在发放糖果,请等待");
    }

    @Override
    public void turnCrank() {
        System.out.println("已按过曲轴,请等待");
    }

    @Override
    public void dispense() {
        int candyCount = gumballMachine.getCandyCount();
        if(candyCount > 0){
            System.out.println("分发一颗糖果");
            candyCount--;
            gumballMachine.setCandyCount(candyCount);
            if(candyCount > 0){
                gumballMachine.setState(gumballMachine.noQuarterState);
                return;
            }
        }

        System.out.println("抱歉,糖果已售尽");
        gumballMachine.setState(gumballMachine.soldOutState);
    }

}

/**
 * 售尽的状态
 */
public class SoldOutState extends State{
    GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("糖果已经售尽");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println("没有投币,无法退币");
    }

    @Override
    public void turnCrank() {
        System.out.println("糖果已经售尽");
    }

    @Override
    public void dispense() {
        System.out.println("糖果已经售尽");
    }

}
  • 将糖果机的动作委托到状态类
public class GumballMachine extends State{
    public State noQuarterState = new NoQuarterState(this);
    public State hasQuarterState = new HasQuarterState(this);
    public State soldState = new SoldState(this);
    public State soldOutState = new SoldOutState(this);

    private State state = soldOutState;
    private int candyCount = 0;

    public GumballMachine(int count) {
        this.candyCount = count;
        if(count > 0)
            setState(noQuarterState);
    }

    @Override
    public void insertQuarter() {
        state.insertQuarter();
    }
    @Override
    public void ejectQuarter() {
        state.ejectQuarter();
    }
    @Override
    public void turnCrank() {
        state.turnCrank();
    }
    @Override
    public void dispense() {
        state.dispense();
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void setCandyCount(int candyCount) {
        this.candyCount = candyCount;
    }

    public int getCandyCount() {
        return candyCount;
    }

}
  • 测试
public class Main {
    public static void main(String[] args) {
        NoPatternGumballMachine noPatternGumballMachine = new NoPatternGumballMachine(100);
        noPatternGumballMachine.insertQuarter();
        noPatternGumballMachine.turnCrank();
        noPatternGumballMachine.ejectQuarter();
        noPatternGumballMachine.dispense();

        System.out.println("==========加入设计模式========");
        GumballMachine gumballMachine = new GumballMachine(100);
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
        gumballMachine.ejectQuarter();
        gumballMachine.dispense();  
    }
}/**
投币
转动曲轴,准备发糖
无法退币,正在发放糖果,请等待
分发一颗糖果
==========加入设计模式========
你投入了一个硬币
转动曲轴,准备发糖
无法退币,正在发放糖果,请等待
分发一颗糖果*/

可以发现,这种设计下,糖果机根本不需要清楚状态的改变,它只用调用状态的方法就行。状态的改变是在状态内部发生的。这就是“状态模式”。

如果此时再增加一种状态,糖果机不需要做任何改变,我们只需要再增加一个状态类,然后在相关的状态类方法里面增加转换的过程即可。

猜你喜欢

转载自blog.csdn.net/z55887/article/details/73198039