前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >17.设计模式--状态模式(State模式)

17.设计模式--状态模式(State模式)

作者头像
大猫的Java笔记
发布2021-11-18 13:19:58
3070
发布2021-11-18 13:19:58
举报
文章被收录于专栏:大猫的Java笔记

1.定义

状态模式也是一种行为型模式,允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

随着城市的发展,现在楼房都是高楼大厦,对于楼房而言,最基础的配置那肯定是电梯了;电梯可以打开电梯门、关闭电梯门、上下运行、以及停止状态。而对应代码则是如下所示。

代码语言:javascript
复制
public interface ILift {

    /**
     * 电梯门开启
     */
    void open();

    /**
     * 电梯门关闭
     */
    void close();

    /**
     * 电梯启动
     */
    void run();

    /**
     * 电梯停止
     */
    void stop();
}
代码语言:javascript
复制
public class LiftImpl implements ILift{

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

    @Override
    public void close() {
        System.out.println("电梯门关闭...");
    }

    @Override
    public void run() {
        System.out.println("电梯启动...");
    }

    @Override
    public void stop() {
        System.out.println("电梯停止...");
    }
}
代码语言:javascript
复制
public class Test {
    public static void main(String[] args) {
        ILift lift = new LiftImpl();
        //打开电梯门,人员上电梯
        lift.open();
        //电梯门关闭
        lift.close();
        //电梯运行中
        lift.run();
        //电梯到达指定楼层停止
        lift.stop();
    }
}

运行结果如下,是不是非常简单,实际上对于open、close、run、stop等方法可以使用模板方法模式。

代码语言:javascript
复制
电梯门开启。。。
电梯门关闭。。。
电梯上下跑起来。。。
电梯停止了

电梯运行改良版

上面的代码非常的简单,但是存在一个问题,电梯并不是所有状态都能干其他的事情,比如电梯运行的时候不能打开门等,这是没有考虑到的点,于是将代码稍作修改变成如下所示。

代码语言:javascript
复制
public interface ILift {

    //电梯的四个状态
    //门敞状态
    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;

    /**
     * 设置电梯状态
     * @param state
     */
    void setState(int state);

    /**
     * 电梯门开启
     */
    void open();

    /**
     * 电梯门关闭
     */
    void close();

    /**
     * 电梯启动
     */
    void run();

    /**
     * 电梯停止
     */
    void stop();
}

下面电梯的实现中,规定了什么状态下能做什么事情,比如对应open方法即打开电梯门,什么状态下能打开电梯门呢?关闭和停止状态。而对于close方法来说,什么情况下能关闭电梯门呢?只有打开门状态。

代码语言:javascript
复制
public class LiftImpl implements ILift{

    private int state;

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

    @Override
    public void open() {
        //什么状态下能打开电梯门
        switch (this.state){
            //如果是开启状态,那么不需要处理
            case OPENING_STATE :
                break;
            //如果是关闭状态,那么是可以开启门的
            case CLOSING_STATE :
                openWithoutLogic();
                setState(CLOSING_STATE);
                break;
            //如果是运行状态,当然这种情况下是不允许开启门的
            case RUNNING_STATE :
                break;
            //如果是停止状态,此时当然可以开启门
            case STOPPING_STATE :
                openWithoutLogic();
                setState(OPENING_STATE);
                break;
        }
    }

    @Override
    public void close() {
        //什么状态下才能关闭电梯门
        switch (this.state){
            //如果是开启状态,可以进行关闭电梯门
            case OPENING_STATE :
                setState(CLOSING_STATE);
                closeWithoutLogic();
                break;
            //如果是关闭状态,那么不需要再关闭
            case CLOSING_STATE :
                break;
            //如果是运行状态,运行时门本来就是关闭的
            case RUNNING_STATE :
                break;
            //如果是停止状态,门本来就是关闭的
            case STOPPING_STATE :
                break;
        }
    }

    @Override
    public void run() {
        //什么状态下才能电梯运行
        switch (this.state){
            //如果是开启状态,不允许运行
            case OPENING_STATE :
                break;
            //如果是关闭状态,可以运行
            case CLOSING_STATE :
                setState(RUNNING_STATE);
                runWithoutLogic();
                break;
            //如果是运行状态,本来就是运行不需要再更改状态
            case RUNNING_STATE :
                break;
            //如果是停止状态,可以运行,其他人使用电梯时
            case STOPPING_STATE :
                setState(RUNNING_STATE);
                runWithoutLogic();
                break;
        }
    }

    @Override
    public void stop() {
        //什么状态下才能电梯停止
        switch (this.state){
            //如果是开启状态,不能停止
            case OPENING_STATE :
                break;
            //如果是关闭状态,可以停止电梯
            case CLOSING_STATE :
                setState(STOPPING_STATE);
                stopWithoutLogic();
                break;
            //如果是运行状态,中间有人下电梯,肯定可以停止
            case RUNNING_STATE :
                setState(STOPPING_STATE);
                stopWithoutLogic();
                break;
            //如果是停止状态,本来就是停止,不需要更改状态
            case STOPPING_STATE :
                break;
        }
    }

    //单纯的电梯门打开,不考虑其他条件
    public void openWithoutLogic(){
        System.out.println("电梯门打开。。。");
    }

    //单纯的电梯门关闭,不考虑其他条件
    public void closeWithoutLogic(){
        System.out.println("电梯门关闭。。。");
    }

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

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

现在电梯的功能比较完善了,也支持了不能随意变化状态,运行结果如下,但是依然存在问题,如果此时要增加一个维修状态,那么所有的方法中都要增加case判断,同时还需要在对应的维修状态下增加switch进行判断,这是完全违背开闭原则的。同时随着状态的增多大量的switch和case使得代码异常的复杂。

代码语言:javascript
复制
public class Test {
    public static void main(String[] args) {
        ILift lift = new LiftImpl();
        lift.setState(ILift.STOPPING_STATE);
        //首先是电梯门开启,人进去
        lift.open();
        //然后电梯门关闭
        lift.close();
        //再然后,电梯跑起来,向上或者向下
        lift.run();
        //最后到达目的地,电梯挺下来
        lift.stop();
    }
}
代码语言:javascript
复制
电梯门打开。。。
电梯门关闭。。。
电梯上下跑起来...
电梯停止了...

2.状态模式实现

电梯运行终极版

现在知道电梯的运行代码存在的问题后,我们尝试使用状态模式进行更改。状态模式将所有的状态抽象为对应的类,然后都继承于LiftState,这样就保证了所有的状态是一致性的。同时使用Context来引用不同的状态,然后根据不同的情况进行状态的流转。这样就实现了文章开头前所说的,允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

LiftState用于约束不同的状态,所有的状态都继承与LiftState,保证状态之间的一致性行为。

代码语言:javascript
复制
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用于保存不同的状态,每一个电梯状态都是一个独立的类,同时电梯在什么状态行能被其他状态给触发,也是通过Context进行流转的。

代码语言:javascript
复制
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();
    }
}

OpnningState状态下,电梯门是打开,所以对于open方法来说只是打开电梯门,而在打开状态下,只能进行关闭,而停止和打开状态在OpnningState状态下本身就是存在的,所以只有关闭状态下可以发生流转,而流转是则是用过Context进行的。

代码语言:javascript
复制
public class OpenningState extends LiftState{
    @Override
    public void open() {
        System.out.println("电梯门开启。。。");
    }

    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.closeingState);
        //动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    @Override
    public void run() {
        //运行不能打开门
    }

    @Override
    public void stop() {
        //开门肯定停止了
    }
}

而ClosingState状态下,电梯门是关闭,所以对于close方法来说只是关闭电梯门,而在关闭状态下,可以打开门和运行以及停止。

代码语言:javascript
复制
public class ClosingState extends LiftState{
    @Override
    public void open() {
        //状态修改
        super.context.setLiftState(Context.openningState);
        //动作委托为openningState来执行
        super.context.getLiftState().open();
    }

    @Override
    public void close() {
        System.out.println("电梯门关闭。。。");
    }

    @Override
    public void run() {
        //关门的状态下可以运行
        //状态修改
        super.context.setLiftState(Context.runningState);
        //动作委托为runningState来执行
        super.context.getLiftState().run();
    }

    @Override
    public void stop() {
        //状态修改,关门状态下可以停止
        super.context.setLiftState(Context.stoppingState);
        //动作委托为runningState来执行
        super.context.getLiftState().stop();
        //停止状态门本来就是关闭的
    }
}

而RunningState状态下,电梯处于运行状态,由于运行状态不能打开门,只能进行停止(其他人上电梯或下电梯)。

代码语言:javascript
复制
public class RunningState extends LiftState{
    @Override
    public void open() {
        //运行状态不能开门,什么也不做
    }

    @Override
    public void close() {
        //运行状态门本来就是关闭,什么也不做
    }

    @Override
    public void run() {
        System.out.println("电梯上下跑起来。。。");
    }

    @Override
    public void stop() {
        //停止状态门下可以运行,存在其他楼层用电梯
        super.context.setLiftState(Context.stoppingState);
        super.context.getLiftState().stop();
    }
}

而StoppingState状态下,电梯处于停止状态,停止状态下可以开门,以及可以运行。

代码语言:javascript
复制
public class StoppingState extends LiftState{
    @Override
    public void open() {
        //停止状态可以开门
        super.context.setLiftState(Context.openningState);
        super.context.getLiftState().open();
    }

    @Override
    public void close() {
        //停止状态门本来就是关闭的,不需要做什么
    }

    @Override
    public void run() {
        //停止状态可以开门
        super.context.setLiftState(Context.runningState);
        super.context.getLiftState().run();
    }

    @Override
    public void stop() {
        System.out.println("电梯停止了");
    }
}

现在只需要通过Context来存放具体的状态类,然后进行电梯的状态,当然这4种状态实际上也可以写一个模板方法,然后进行调用对应的模板。

代码语言:javascript
复制
public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(Context.stoppingState);
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}
代码语言:javascript
复制
电梯门打开。。。
电梯门关闭。。。
电梯上下跑起来...
电梯停止了...

状态模式中的角色

State(状态),State角色,定义了不同的状态,例如电梯的所有状态的抽象,文中由LiftState扮演此角色

ConcreteState(具体的角色),ConcreteState角色,是对State角色的具体实现,文中由OpenningState、ClosingState、RunningState、StoppingState扮演此角色。

Context(状况、状态上下文),Context角色,是对状态进行流转,以及存放状态类的地方,文中由Context扮演此角色。

参考文献《设计模式之禅》

代码获取地址:https://gitee.com/bughong/design-pattern

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大猫的Java笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档