1

下面用一个例子来帮助大家理解状态模式

我们每天都在乘电梯, 那我们来看看电梯有哪些动作(映射到Java中就是有多少方法) : 开门、 关门、运行、停止。好,我们就用程序来实现一下电梯的动作.

电梯程序第一版

首先我们定义一个电梯接口

在坐电梯时,你不可能希望电梯在运行期间突然开门吧,也不希望电梯停下了打不开门吧,所以,电梯执行这几个动作之前都需要前置条件。

  • 敞门状态:在这个状态下电梯只能做的动作是关门动作。
  • 闭门状态:在这个状态下, 可以进行的动作是:开门(我不想坐电梯了)、 停止(忘记按路层号了)、运行。
  • 运行状态:在这个状态,电梯只能做停止。
  • 停止状态:可以选择继续运行和开门

image-20200530165048512

下面我们看一下电梯程序的类图

image-20200530122004296

这里增加了4个静态常量, 并增加了一个方法setState, 设置电梯的状态。

public interface ILift {
    //电梯的4个状态
    public final static int OPENING_STATE = 1; //敞门状态
    public final static int CLOSING_STATE = 2; //闭门状态
    public final static int RUNNING_STATE = 3; //运行状态
    public final static int STOPPING_STATE = 4; //停止状态

    //设置电梯的状态
    public void setState(int state);

    //首先电梯门开启动作
    public void open();

    //电梯门可以开启,那当然也就有关闭了
    public void close();

    //电梯要能上能下,运行起来
    public void run();
    
    //电梯还要能停下来
    public void stop();
}

电梯实现类

public class Lift implements ILift {
    private int state;

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

    //电梯门关闭
    public void close() {
        //电梯在什么状态下才能关闭
        switch (this.state) {
            case OPENING_STATE: //可以关门, 同时修改电梯状态
                this.closeWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE: //电梯是关门状态, 则什么都不做
                //do nothing;
                break;
            case RUNNING_STATE: //正在运行, 门本来就是关闭的, 也什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 门也是关闭的, 什么也不做
                //do nothing;
                break;
        }
    }

    //电梯门开启
    public void open() {
        //电梯在什么状态才能开启
        switch (this.state) {
            case OPENING_STATE: //闭门状态, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则可以开启
                this.openWithoutLogic();
                this.setState(OPENING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 则不能开门, 什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 当然要开门了
                this.openWithoutLogic();
                this.setState(OPENING_STATE);
                break;
        }
    }

    //电梯开始运行起来
    public void run() {
        switch (this.state) {
            case OPENING_STATE: //敞门状态, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则可以运行
                this.runWithoutLogic();
                this.setState(RUNNING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 则什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 可以运行
                this.runWithoutLogic();
                this.setState(RUNNING_STATE);
        }
    }

    //电梯停止
    public void stop() {
        switch (this.state) {
            case OPENING_STATE: //敞门状态, 要先停下来的, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则当然可以停止了
                this.stopWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 有运行当然那也就有停止了
                this.stopWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case STOPPING_STATE: //停止状态, 什么都不做
                //do nothing;
                break;
        }
    }

    //纯粹的电梯关门, 不考虑实际的逻辑
    private void closeWithoutLogic() {
        System.out.println("电梯门关闭...");
    }

    //纯粹的电梯开门, 不考虑任何条件
    private void openWithoutLogic() {
        System.out.println("电梯门开启...");
    }

    //纯粹的运行, 不考虑其他条件
    private void runWithoutLogic() {
        System.out.println("电梯上下运行起来...");
    }

    //单纯的停止, 不考虑其他条件
    private void stopWithoutLogic() {
        System.out.println("电梯停止了...");
    }
}   

场景类

public class Client {
    public static void main(String[] args) {
        ILift lift = new Lift();
        
        //电梯的初始条件应该是停止状态
        lift.setState(ILift.STOPPING_STATE);
        
        //首先是电梯门开启, 人进去
        lift.open();
        
        //然后电梯门关闭
        lift.close();
        
        //再然后, 电梯运行起来, 向上或者向下
        lift.run();
        
        //最后到达目的地, 电梯停下来
        lift.stop();
    }
}

在业务调用的方法中增加了电梯状态判断, 电梯要不是随时都可以开的, 必须满足一定
条件才能开门, 人才能走进去, 我们设置电梯的起始是停止状态 。

运行结果

image-20200530123522191

这段程序的问题:

  • 电梯实现类过长,长的原因是我们在程序中使用了大量的switch...case这样的判断 。
  • 扩展性差:如果程序还要加上通电状态和断电状态,Open()、 Close()、 Run()、 Stop()这4个方法都要增加判断条件 。

电梯程序第二版--状态模式

下图为电梯程序的以状态作为导向的类图

image-20200530124630190

在类图中, 定义了一个LiftState抽象类, 声明了一个受保护的类型Context变量, 这个是
串联各个状态的封装类。 封装的目的很明显, 就是电梯对象内部状态的变化不被调用类知
晓, 也就是迪米特法则了(我的类内部情节你知道得越少越好) , 并且还定义了4个具体的
实现类, 承担的是状态的产生以及状态间的转换过渡 。

抽象电梯状态

public abstract class LiftState {
    //定义一个环境角色, 也就是封装状态的变化引起的功能变化
    protected Context context;

    public void setContext(Context _context) {
        this.context = _context;
    }

    //首先电梯门开启动作
    public abstract void open();

    //电梯门有开启, 那当然也就有关闭了
    public abstract void close();

    //电梯要能上能下, 运行起来
    public abstract void run();

    //电梯还要能停下来
    public abstract void stop();
}

上下文类Context

public class Context {
    //定义出所有的电梯状态
    public final static OpenningState openningState = new OpenningState();
    public final static ClosingState closeingState = new ClosingState();
    public final static RunningState runningState = new RunningState();
    public final static StoppingState stoppingState = new StoppingState();
    
    //定一个当前电梯状态
    private LiftState liftState;
    
    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }
    
    public void open(){
        this.liftState.open();
    }
    
    public void close(){
        this.liftState.close();
    }
    
    public void run(){
        this.liftState.run();
    }
    
    public void stop(){
        this.liftState.stop();
    }
}

敞门状态实现

public class OpenningState extends LiftState {
    //开启当然可以关闭了, 我就想测试一下电梯门开关功能
    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.closeingState);
        //动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    //打开电梯门
    @Override
    public void open() {
        System.out.println("电梯门开启...");
    }

    //门开着时电梯就运行跑, 这电梯, 吓死你!
    @Override
    public void run() {
        //do nothing;
    }

    //开门还不停止?
    public void stop() {
        //do nothing;
    }
}

关门状态实现

public class ClosingState extends LiftState {
    //电梯门关闭, 这是关闭状态要实现的动作
    @Override
    public void close() {
        System.out.println("电梯门关闭...");
    }

    //电梯门关了再打开
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState); //置为敞门状态
        super.context.getLiftState().open();
    }

    //电梯门关了就运行, 这是再正常不过了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState); //设置为运行状态
        super.context.getLiftState().run();
    }

    //电梯门关着, 我就不按楼层
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //设置为停止状态
        super.context.getLiftState().stop();
    }
}

运行和停止状态实现与上面两种类似

场景测试类

public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

状态模式的应用

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

image-20200530165520355

我们先来看看状态模式中的3个角色。
● State——抽象状态角色
接口或抽象类, 负责对象状态定义, 并且封装环境角色以实现状态切换。
● ConcreteState——具体状态角色
每一个具体状态必须完成两个职责: 本状态的行为管理以及趋向状态处理, 通俗地说,
就是本状态下要做的事情, 以及本状态如何过渡到其他状态。
● Context——环境角色
定义客户端需要的接口, 并且负责具体状态的切换。

状态模式的优缺点

优点

  • 结构清晰,避免了过多的switch...case或者if...else语句的使用, 避免了程序的复杂性,提高系统的可
    维护性。
  • 遵循设计原则,很好地体现了开闭原则和单一职责原则, 每个状态都是一个子类, 你要增加状态就要增
    加子类, 你要修改状态, 你只修改一个子类就可以了。
  • 封装性好,这也是状态模式的基本要求, 状态变换放置到类的内部来实现, 外部的调用不用知道类
    内部如何实现状态和行为的变换。

缺点

  • 子类会太多, 也就是类膨胀

状态模式的使用场景

  • 行为随状态改变而改变的场景
  • 条件、 分支判断语句的替代者:在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰, 逻辑混乱, 使用状态模式可以很好地避免这一问题, 它通过扩展子类实现了条件的判断处理

下面用一个例子来帮助大家理解状态模式

我们每天都在乘电梯, 那我们来看看电梯有哪些动作(映射到Java中就是有多少方法) : 开门、 关门、运行、停止。好,我们就用程序来实现一下电梯的动作.

电梯程序第一版

首先我们定义一个电梯接口

在坐电梯时,你不可能希望电梯在运行期间突然开门吧,也不希望电梯停下了打不开门吧,所以,电梯执行这几个动作之前都需要前置条件。

  • 敞门状态:在这个状态下电梯只能做的动作是关门动作。
  • 闭门状态:在这个状态下, 可以进行的动作是:开门(我不想坐电梯了)、 停止(忘记按路层号了)、运行。
  • 运行状态:在这个状态,电梯只能做停止。
  • 停止状态:可以选择继续运行和开门

image-20200530165048512

下面我们改一下之前做的程序,看一下修改之后的类图

image-20200530122004296

这里增加了4个静态常量, 并增加了一个方法setState, 设置电梯的状态。

public interface ILift {
    //电梯的4个状态
    public final static int OPENING_STATE = 1; //敞门状态
    public final static int CLOSING_STATE = 2; //闭门状态
    public final static int RUNNING_STATE = 3; //运行状态
    public final static int STOPPING_STATE = 4; //停止状态

    //设置电梯的状态
    public void setState(int state);

    //首先电梯门开启动作
    public void open();

    //电梯门可以开启,那当然也就有关闭了
    public void close();

    //电梯要能上能下,运行起来
    public void run();
    
    //电梯还要能停下来
    public void stop();
}

电梯实现类

public class Lift implements ILift {
    private int state;

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

    //电梯门关闭
    public void close() {
        //电梯在什么状态下才能关闭
        switch (this.state) {
            case OPENING_STATE: //可以关门, 同时修改电梯状态
                this.closeWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE: //电梯是关门状态, 则什么都不做
                //do nothing;
                break;
            case RUNNING_STATE: //正在运行, 门本来就是关闭的, 也什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 门也是关闭的, 什么也不做
                //do nothing;
                break;
        }
    }

    //电梯门开启
    public void open() {
        //电梯在什么状态才能开启
        switch (this.state) {
            case OPENING_STATE: //闭门状态, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则可以开启
                this.openWithoutLogic();
                this.setState(OPENING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 则不能开门, 什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 当然要开门了
                this.openWithoutLogic();
                this.setState(OPENING_STATE);
                break;
        }
    }

    //电梯开始运行起来
    public void run() {
        switch (this.state) {
            case OPENING_STATE: //敞门状态, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则可以运行
                this.runWithoutLogic();
                this.setState(RUNNING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 则什么都不做
                //do nothing;
                break;
            case STOPPING_STATE: //停止状态, 可以运行
                this.runWithoutLogic();
                this.setState(RUNNING_STATE);
        }
    }

    //电梯停止
    public void stop() {
        switch (this.state) {
            case OPENING_STATE: //敞门状态, 要先停下来的, 什么都不做
                //do nothing;
                break;
            case CLOSING_STATE: //闭门状态, 则当然可以停止了
                this.stopWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case RUNNING_STATE: //运行状态, 有运行当然那也就有停止了
                this.stopWithoutLogic();
                this.setState(CLOSING_STATE);
                break;
            case STOPPING_STATE: //停止状态, 什么都不做
                //do nothing;
                break;
        }
    }

    //纯粹的电梯关门, 不考虑实际的逻辑
    private void closeWithoutLogic() {
        System.out.println("电梯门关闭...");
    }

    //纯粹的电梯开门, 不考虑任何条件
    private void openWithoutLogic() {
        System.out.println("电梯门开启...");
    }

    //纯粹的运行, 不考虑其他条件
    private void runWithoutLogic() {
        System.out.println("电梯上下运行起来...");
    }

    //单纯的停止, 不考虑其他条件
    private void stopWithoutLogic() {
        System.out.println("电梯停止了...");
    }
}   

场景类

public class Client {
    public static void main(String[] args) {
        ILift lift = new Lift();
        
        //电梯的初始条件应该是停止状态
        lift.setState(ILift.STOPPING_STATE);
        
        //首先是电梯门开启, 人进去
        lift.open();
        
        //然后电梯门关闭
        lift.close();
        
        //再然后, 电梯运行起来, 向上或者向下
        lift.run();
        
        //最后到达目的地, 电梯停下来
        lift.stop();
    }
}

在业务调用的方法中增加了电梯状态判断, 电梯要不是随时都可以开的, 必须满足一定
条件才能开门, 人才能走进去, 我们设置电梯的起始是停止状态 。

运行结果

image-20200530123522191

这段程序的问题:

  • 电梯实现类过长,长的原因是我们在程序中使用了大量的switch...case这样的判断 。
  • 扩展性差:如果程序还要加上通电状态和断电状态,Open()、 Close()、 Run()、 Stop()这4个方法都要增加判断条件 。

电梯程序第二版--状态模式

下图为电梯程序的以状态作为导向的类图

image-20200530124630190

在类图中, 定义了一个LiftState抽象类, 声明了一个受保护的类型Context变量, 这个是
串联各个状态的封装类。 封装的目的很明显, 就是电梯对象内部状态的变化不被调用类知
晓, 也就是迪米特法则了(我的类内部情节你知道得越少越好) , 并且还定义了4个具体的
实现类, 承担的是状态的产生以及状态间的转换过渡 。

抽象电梯状态

public abstract class LiftState {
    //定义一个环境角色, 也就是封装状态的变化引起的功能变化
    protected Context context;

    public void setContext(Context _context) {
        this.context = _context;
    }

    //首先电梯门开启动作
    public abstract void open();

    //电梯门有开启, 那当然也就有关闭了
    public abstract void close();

    //电梯要能上能下, 运行起来
    public abstract void run();

    //电梯还要能停下来
    public abstract void stop();
}

上下文类Context

public class Context {
    //定义出所有的电梯状态
    public final static OpenningState openningState = new OpenningState();
    public final static ClosingState closeingState = new ClosingState();
    public final static RunningState runningState = new RunningState();
    public final static StoppingState stoppingState = new StoppingState();
    
    //定一个当前电梯状态
    private LiftState liftState;
    
    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }
    
    public void open(){
        this.liftState.open();
    }
    
    public void close(){
        this.liftState.close();
    }
    
    public void run(){
        this.liftState.run();
    }
    
    public void stop(){
        this.liftState.stop();
    }
}

敞门状态实现

public class OpenningState extends LiftState {
    //开启当然可以关闭了, 我就想测试一下电梯门开关功能
    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.closeingState);
        //动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    //打开电梯门
    @Override
    public void open() {
        System.out.println("电梯门开启...");
    }

    //门开着时电梯就运行跑, 这电梯, 吓死你!
    @Override
    public void run() {
        //do nothing;
    }

    //开门还不停止?
    public void stop() {
        //do nothing;
    }
}

关门状态实现

public class ClosingState extends LiftState {
    //电梯门关闭, 这是关闭状态要实现的动作
    @Override
    public void close() {
        System.out.println("电梯门关闭...");
    }

    //电梯门关了再打开
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState); //置为敞门状态
        super.context.getLiftState().open();
    }

    //电梯门关了就运行, 这是再正常不过了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState); //设置为运行状态
        super.context.getLiftState().run();
    }

    //电梯门关着, 我就不按楼层
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //设置为停止状态
        super.context.getLiftState().stop();
    }
}

运行和停止状态实现与上面两种类似

场景测试类

public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

状态模式的应用

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

image-20200530165520355

我们先来看看状态模式中的3个角色。
● State——抽象状态角色
接口或抽象类, 负责对象状态定义, 并且封装环境角色以实现状态切换。
● ConcreteState——具体状态角色
每一个具体状态必须完成两个职责: 本状态的行为管理以及趋向状态处理, 通俗地说,
就是本状态下要做的事情, 以及本状态如何过渡到其他状态。
● Context——环境角色
定义客户端需要的接口, 并且负责具体状态的切换。

状态模式的优缺点

优点

  • 结构清晰,避免了过多的switch...case或者if...else语句的使用, 避免了程序的复杂性,提高系统的可
    维护性。
  • 遵循设计原则,很好地体现了开闭原则和单一职责原则, 每个状态都是一个子类, 你要增加状态就要增
    加子类, 你要修改状态, 你只修改一个子类就可以了。
  • 封装性好,这也是状态模式的基本要求, 状态变换放置到类的内部来实现, 外部的调用不用知道类
    内部如何实现状态和行为的变换。

缺点

  • 子类会太多, 也就是类膨胀

状态模式的使用场景

  • 行为随状态改变而改变的场景
  • 条件、 分支判断语句的替代者:在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰, 逻辑混乱, 使用状态模式可以很好地避免这一问题, 它通过扩展子类实现了条件的判断处理

参考书籍:《设计模式之禅》


thirtyyy
16 声望0 粉丝

生活如水,时而浑浊,时而清澈