对“状态模式”的认识

顾名思义,这个模式是当类的行为基于其内部状态的时候,并且可以根据状态的改变而改变它的相关行为的时候

主要在代码上的表现就是代码中包含着许多If else语句,这时候或者你就应该提前想想是不是该使用状态模式了;稍后将会对状态模式进行分析

首先看一个错误的例子(Head First设计模式书中,我抄上吧),虽然它名字上体现了State,但实际上并不是状态模式,或者说没能达到状态模式的精髓;

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 currentState=SOLD_OUT;

    int count=0;
    public GumballMachine(int count){
        //进行初始化
        this.count=count;
        if(count>0){
            this.currentState=No_QUARTER;
        }
    }

    //定义行为
    public void insertQuarter(){
        if(currentState==HAS_QUARTER){
            System.out.println("你不能再投一硬币");
        }else if(currentState==No_QUARTER){
            System.out.println("你投入了一个硬币");
            currentState=HAS_QUARTER;
        }else if(currentState==SOLD_OUT){
            System.out.println("糖果机没糖果了,不要再往里面加钱了");
        }else if(currentState==SOLD){
            System.out.println("稍等一下");
        }
    }

    public void ejectQuarter(){
        //some operations like the one above;
    }

    //another functions
}

可以看到上面的代码方法中用了很多if else语句,并且类很明显与当前状态相关,此时改变出现,要求新增一个操作,此时你不得不修改其他所有方法的代码,因为状态转换是由Context(环境,在这就是糖果机,这个词挺关键,中文意指上下文语境,很多人翻译成上下文,当时听的懵逼,现在稍稍懂了一点,其实就是环境,或者说配置环境,为当前操作提供所需的所有参数,具体的操作依赖具体环境才能进行)完成的;

再稍微懂点状态模式的情况下,你得重新改写你的代码以适应新需求(这与开放-关闭原则相悖,事实上有经验的程序员会一开始就使用此模式,因为这种模式的出现情况很容易识别,而且像此类需求,你是凭借扩充是办不了的,必须修改之前代码)

新的代码是

抽出变化的部份,即状态

public interface State {
    //定义糖果机行为,一般的情况下Context提供行为是确定的,变化的是状态,牢记这一点
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

实现状态操作

public class NoQuarterState implements State {
    private GumballMachine machine;
    public NoQuarterState(GumballMachine context){
        this.machine=context;
    }
    @Override
    public void insertQuarter() {
        //A state transition may occur
    }

    @Override
    public void ejectQuarter() {

    }

    @Override
    public void turnCrank() {

    }

    @Override
    public void dispense() {

    }
}

如上,状态转换是在状态实现类里面发生的,Context对象不接管状态的转换,状态被彻底抽了出来

新的糖果机

public class GumballMachine {
    private State currentState=new NoQuarterState(this);
    private int count=0;
    public GumballMachine(int count){
        //进行初始化
        this.count=count;
    }
    //操作
    public void insertQuarter(){
        currentState.insertQuarter();
    }
    public void ejectQuarter(){
        currentState.ejectQuarter();
    }
    // ...
    public void setCurrentState(State state){
        this.currentState=state;
    }
}

看看此时新增加状态时将会怎么变,如新增加WinnerState

public class WinnerState implements State {
    private GumballMachine machine;
    public WinnerState(GumballMachine context){
        this.machine=context;
    }
    @Override
    public void insertQuarter() {
        
    }

    @Override
    public void ejectQuarter() {
        // A state transition may occur
    }

    @Override
    public void turnCrank() {

    }

    @Override
    public void dispense() {

    }
}

因为状态转换完全封装在了状态对象里面,所以糖果机不需要改动

细心的同学可能会发现不少问题

首先 当新状态增加或者删除掉的时候,万一增加的状态对其他状态有依赖怎么办,还是得修改其他类的源代码,还是不行啊;

对于此类问题,我的理解是,谨慎设计代码后还是有状态明显以来其他状态,当某一状态发生修改,其他状态少不了要修改,这是免不了的,但是我们利用此模式,能将修改程度做到尽量小,在此模式中你只需修改与发生变化的状态有依赖的状态就好,而之前那种做法,你不得不修改每一个方法并且仔细检查,出错程度降低了,而且代码逻辑清晰,易于理解,别人一看你这是状态模式,就会联想到怎么处理

第二 实现上的问题 上面代码可以进一步优化,对Context这个东西而言,暴露了setState(State)方法,这对外部cliente而言是个很不知所措的接口,client会不知道怎么使用(因为他根本就不是给client使用的,是给State使用的),而且State作用范围也有待考量,很明显,像此类代码State只会服务于此糖果机,因为你在定义State时候定义的方法就是Context的方法,因为决定了State相关类只能服务于特定Context(此处希望正确理解),不应该暴露出去,而且根据我的经验判断,所有基于状态的类的设计都是这个套路

综合以上,新版的实现如下:

public class GumballMachine {
    private State currentState;
    private int count=0;
    public GumballMachine(int count){
        //进行初始化
        this.count=count;
    }
    //操作
    public void insertQuarter(){
        currentState.insertQuarter();
    }
    public void ejectQuarter(){
        currentState.ejectQuarter();
    }
    // ...对State开放的接口,不对client开放,因此声明为private,若此类是为继承而设计的类,请声明为protected或者酌情设计访问权限
    private void setCurrentState(State state){
        this.currentState=state;
    }
    
    //因为State接口只服务于Context,因而将其声明为 private static
    private static interface State{
            //定义糖果机行为,一般的情况下Context提供行为是确定的,变化的是状态,牢记这一点
            void insertQuarter();
            
            void ejectQuarter();
            
            void turnCrank();
            
            void dispense();
    }
    private static class NoQuarterState implements State{
        private GumballMachine machine;
        public NoQuarterState(GumballMachine context){
            this.machine=context;
        }
        @Override
        public void insertQuarter() {
            //A state transition may occur
        }

        @Override
        public void ejectQuarter() {

        }

        @Override
        public void turnCrank() {

        }

        @Override
        public void dispense() {

        }
    } 
}

如上的实现觉得应该是比较好的了,如有细节或者心得讨论请联系我 2900250200 QQ

 ********************************************分割线*************************************************

与策略模式的区别,区别大了你问区别?当然都是用组合实现的,具体操作委托给相关组合对象了,但是里面差别很大,

1.目的上的区别是根本的,策略模式:将可以互换的行为封装起来,然后使用委托的方法决定使用哪一个行为,状态模式:封装基于状态的行为,并将行为委托到当前状态,不要因为都是用组合实现的,这意图上的就十分明显

2.实现上,如第一条 策略是可以互换的行为,由client完成,这就决定了Context需要向client暴露其setStrategy(Strategy),而状态是有依赖关系的,是转化过去,而非互换,由状态内部自己完成,一般不向client暴露;

猜你喜欢

转载自blog.csdn.net/WK_SDU/article/details/82185488