美文网首页设计模式
设计模式之禅-状态模式

设计模式之禅-状态模式

作者: 凉快先生 | 来源:发表于2021-01-04 15:59 被阅读0次

我们每天都在坐电梯,电梯有“开门、关门、运行、停止”四个动作

个电梯的接口:

public interface ILift {

        //首先电梯门开启动作

        public void open();

        //电梯门有开启,那当然也就有关闭了

        public void close();

        //电梯要能上能下,跑起来

        public void run();

        //电梯还要能停下来,停不下来那就扯淡了

        public void stop();

}

电梯实现类:

public class Lift implements ILift {

    //电梯门关闭

    public void close() {

        System.out.println("电梯门关闭...");

    }

    //电梯门开启

    public void open() {

        System.out.println("电梯门开启...");

    }

    //电梯开始跑起来

    public void run() {

        System.out.println("电梯上下跑起来...");

    }

    //电梯停止

    public void stop() {

        System.out.println("电梯停止了...");

    }

}

模拟电梯的动作:

public class Client {

    public static void main(String[] args) {

        ILift lift = new Lift();

        //首先是电梯门开启,人进去

        lift.open();

        //然后电梯门关闭

        lift.close();

        //再然后,电梯跑起来,向上或者向下

        lift.run();

        //最后到达目的地,电梯挺下来

        lift.stop();

    }

}

这个程序有什么问题,你想呀电梯门可以打开,但不是随时都可以开,是有前提条件的的,你不可能电梯在运行的时候突然开门吧?!电梯也不会出现停止了但是不开门的情况吧?!那要是有也是事故嘛,再仔细想想,电梯的这四个动作的执行都是有前置条件,具体点说说在特定状态下才能做特定事,那我们来分析一下电梯有什么那些特定状态:

门敞状态---按了电梯上下按钮,电梯门开,在这个状态下电梯只能做的动作是关门动作

门闭状态---电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了)、停止(忘记按路层号了)、运行

运行状态---电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止;

停止状态---电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作;

电梯状态和动作对应表(○表示不允许,☆表示允许动作)

我们修改一下类图:

在接口中定义了四个常量,分别表示电梯的四个状态:门敞状态、关闭状态、运行状态、停止状态,然后在实现类中电梯的每一次动作发生都要对状态进行判断,判断是否运行执行,也就是动作的执行是否符合业务逻辑,实现类中的四个私有方法是仅仅实现电梯的动作,没有任何的前置条件,因此这四个方法是不能为外部类调用的,设置为私有方法。我们先看接口的改变:

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; //停止状态;

    //设置电梯的状态

    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();

    }

}

看运行结果:

电梯门开启...

电梯门关闭...

电梯上下跑起来...

电梯停止了...

这段代码用了大量的swatch...case,如果新增一个状态维护起来会非常麻烦。

我们只要实现电梯在一个状态下的两个任务模型就可以了:这个状态是如何产生的以及在这个状态下还能做什么其他动作(也就是这个状态怎么过渡到其他状态),既然我们以状态为参考模型,那我们就先定义电梯的状态接口,思考过后我们来看类图

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

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();

}

在电梯门开启的状态下能做什么事情:

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;

    }

}

Openning 状态是由 open()方法产生的,因此这个方法中有一个具体的业务逻辑,我们是用 print 来代替了;在 Openning 状态下,电梯能过渡到其他什么状态呢?按照现在的定义的是只能过渡到 Closing 状态,因此我们在 Close()中定义了状态变更,同时把 Close 这个动作也委托了给 CloseState 类下的 Close 方法执行,这个可能不好理解,我们再看看 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();

    }

}

结合以上三个类,我们可以这样理解,Context 是一个环境角色,它的作用是串联各个状态的过渡,在LiftSate 抽象类中我们定义了并把这个环境角色聚合进来,并传递到了子类,也就是四个具体的实现类中自己根据环境来决定如何进行状态的过渡。我们把其他的三个具体实现类阅读完毕,下面是关闭状态:

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 RunningState extends LiftState {

    //电梯门关闭?这是肯定了

    @Override

    public void close() {

        //do nothing

    }

    //运行的时候开电梯门?你疯了!电梯不会给你开的

    @Override

    public void open() {

        //do nothing

    }

    //这是在运行状态下要实现的方法

    @Override

    public void run() {

        System.out.println("电梯上下跑...");

    }

    //这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了

    @Override

    public void stop() {

        super.context.setLiftState(Context.stoppingState); //环境设置为停止状态;

        super.context.getLiftState().stop();

    }

}

停止状态:

public class StoppingState extends LiftState {

    //停止状态关门?电梯门本来就是关着的!

    @Override

    public void close() {

        //do nothing;

    }

    //停止状态,开门,那是要的!

    @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() {

        System.out.println("电梯停止了...");

    }

}

模拟电梯的动作

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();

    }

}

只要定义个电梯的初始状态,然后调用相关的方法,就完成了,完全不用考虑状态的变更

什么是状态模式呢?当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。通用实现类如下:

状态模式中有什么优点呢?首先是避免了过多的 swith…case 或者 if..else 语句的使用,避免了程序的复杂性;其次是很好的使用体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了;最后一个好处就是封装性非常好,这也是状态模式的基本要求,状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。

状态模式既然有优点,那当然有缺点了,只有一个缺点,子类会太多,也就是类膨胀。

相关文章

网友评论

    本文标题:设计模式之禅-状态模式

    本文链接:https://www.haomeiwen.com/subject/gcymoktx.html