下面用一个例子来帮助大家理解状态模式
我们每天都在乘电梯, 那我们来看看电梯有哪些动作(映射到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判断语句会导致程序结构不清晰, 逻辑混乱, 使用状态模式可以很好地避免这一问题, 它通过扩展子类实现了条件的判断处理
参考书籍:《设计模式之禅》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。