设计模式:状态模式

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

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

  下面我们看看状态模式的场景

    场景1:有一个扭糖果机器,在投入1元硬币后,扭动曲柄,然后机器掉出一颗糖果。

  先来看看场景,既然是状态模式,顾名思义,就是根据状态来划分,在这个过程中我们可以找出糖果机所有的状态:有钱,没钱,糖果售罄,出售糖果四种状态,而动作又:投入钱,退回钱,转到曲柄,发放糖果四种,现在我们根据这四种状态和四种动作来进行编码,如下:

//糖果机器类
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;
        }
    }
    
    //现在我们开始实现四个动作:投入钱,退回钱,转到曲柄,发放糖果
    //投入钱
    public void insertQuarter(){
        switch(state){
            //机器状态是有钱状态的时候,不能再投钱了
            case HAS_QUARTER:System.out.println("你不能在投入第二个硬币了");break;
            //机器状态是没钱状态,投入后,机器状态变成有钱状态
            case NO_QUARTER:state = HAS_QUARTER;System.out.println("你投入了1元钱");break;
            //机器状态是售罄
            case SOLD_OUT:System.out.println("已经没有糖果了,不能投币了");break;
            //机器状态是出售糖果,为了让机器把状态给转换过来回复到NO_QUARTER
            case SOLD:System.out.println("请等待,正在准备糖果");break;
        }
    }
    
    //退回钱
    public void ejectQuarter(){
        switch(state){
            //机器状态是有钱状态的时候,退钱
            case HAS_QUARTER:System.out.println("退回钱");state = NO_QUARTER;break;
            //机器状态是没钱状态,就没的退
            case NO_QUARTER:System.out.println("你并未投入钱");break;
            //机器状态是售罄
            case SOLD_OUT:System.out.println("已经没有糖果了,不接受钱,更不可能退钱");break;
            //机器状态是出售糖果,就不能退了
            case SOLD:System.out.println("正在出售糖果,无法退钱");break;
        }
    }
    
    //转动曲柄
    public void turnCrank(){
        switch(state){
            //机器状态是有钱状态,扭动后改变状态为出售糖果,然后调用dispense方法来操作糖果的发放
            case HAS_QUARTER:System.out.println("扭动成功");state = SOLD;dispense();break;
            //机器状态是没钱状态
            case NO_QUARTER:System.out.println("你需要投入钱先");break;
            //机器状态是售罄
            case SOLD_OUT:System.out.println("已经没有糖果了");break;
            //机器状态是出售糖果
            case SOLD:System.out.println("正在出售糖果,此操作无效");break;
        }
    }
    
    //发放糖果
    public void dispense(){
        if(state == SOLD){
            System.out.println("正在准备发放糖果");
            count --;
            if(count == 0){
                System.out.println("最后一颗糖果发放完了,糖果已售罄了");
                state = SOLD_OUT;
            }
            else{
                System.out.println("发放完了糖果");
                state = NO_QUARTER;
            }
        }
    }
}

  让我们写个测试类来测试下

public class Test {
    public static void main(String[] args) {
        //准备5颗糖
        GumballMachine gumballMachine = new GumballMachine(5);
        
        //投入钱
        gumballMachine.insertQuarter();
        //扭动曲柄
        gumballMachine.turnCrank();
        //在投入钱
        gumballMachine.insertQuarter();
        //退钱
        gumballMachine.ejectQuarter();
        //没钱的时候,扭动曲柄
        gumballMachine.turnCrank();
        //没钱的时候,退钱
        gumballMachine.ejectQuarter();
        //投入钱
        gumballMachine.insertQuarter();
        //再投入钱
        gumballMachine.insertQuarter();
    }
}

  运行结果如下

  

  看起来状态模式就是这么简单了,但是如果扩展程序,加上其它状态那么我们就需要在四个动作中分别加入新的判断,这显得很麻烦,而且是不是也违背了我们的封装变化这原则呢,所以我们可以从新来设计下更合理的状态模式。

  首先,我们定义一个State接口,在这个接口内,糖果机器每个动作都有一个对应的方法

//状态接口,每个状态都实现它里面的方法
public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

  然后为机器中的每个状态实现状态类,这些类负责在对应的状态下,机器会是怎么样的行为。

//四大状态之没有钱状态
public class NoQuarterState implements State{
    GumballMachine gumballMachine;
    
    //通过构造器得到糖果机器的引用
    public NoQuarterState(GumballMachine gumballMachine){
        this.gumballMachine = gumballMachine;
    }
    
    @Override
    public void insertQuarter() {
       //因为确定是没有钱状态下投入钱,所以不需要理会其他状态
        System.out.println("你投入了1元钱");
        //将机器的状态变成有钱状态
        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("你并未投入钱,所以没用在发放糖");        
    }
}


//四大状态之有钱状态
public 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("退回钱了");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    @Override
    public void turnCrank() {
        System.out.println("扭动成功");
        gumballMachine.setState(gumballMachine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("操作错误,机器还没到出售糖果的阶段");     
    }
}


//四大状态之出售状态
public 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){
            System.out.println("发放糖果完成");
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        }
        else{
            System.out.println("最后一颗糖果发放完了,糖果已售罄了");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
}

//四大状态之售罄状态
public class SoldOutState implements State{
    GumballMachine gumballMachine;
    
    public SoldOutState(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() {
        System.out.println("已经没有糖果了,此操作状态无效");
    }
}

  最后我们将之前旧代码的条件取而代之为动作委托,委托到相应的状态类。

//糖果机器类
public class GumballMachine {
//将所有状态都定义都机器中
    State soldState;
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    
    State state = soldOutState;
    int count = 0;
    
    public GumballMachine(int count){
        soldOutState = new SoldOutState(this);
        soldState = new SoldState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(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();
    }
    
    
    //用于计算机器中糖果剩余量
    public void releaseBall(){
        System.out.println("正在发放糖果中...");
        if(count !=0){
            count -- ;
        }
    }
    
   //get/set方法
}

  测试代码不变,我们可以看到结果如下:

  

  好了对比之前的,我们做了以下几点:

    1.将每个状态的行为局部化到它自己的类中

    2.将容易产生问题的判断条件删除,以方便日后的维护

    3.让每个状态对修改关闭,对扩展开发,即使加入新的状态也没关系

  最后,让我们再来区分区分状态、策略、模板方法模式。

    状态:封装基于状态的行为,并将行为委托到当前状态。

    策略:将可以互换的行为封装起来,然后使用委托的方法决定使用哪一个行为。

    模板方法:由子类决定如何实现算法中的某些步骤。   

  下一节:代理模式

作者:哀&RT
出处:博客园哀&RT的技术博客--http://www.cnblogs.com/Tony-Anne/
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/Tony-Anne/p/6527920.html