设计模式状态模式

28次阅读

共计 11317 个字符,预计需要花费 29 分钟才能阅读完成。

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

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

电梯程序第一版

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

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

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

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

这里增加了 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();}
}

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

运行结果

这段程序的问题:

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

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

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

在类图中,定义了一个 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();}
}

状态模式的应用

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

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

状态模式的优缺点

优点

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

缺点

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

状态模式的使用场景

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

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

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

电梯程序第一版

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

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

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

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

这里增加了 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();}
}

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

运行结果

这段程序的问题:

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

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

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

在类图中,定义了一个 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();}
}

状态模式的应用

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

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

状态模式的优缺点

优点

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

缺点

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

状态模式的使用场景

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

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

正文完
 0