引例
在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为 有状态的对象
,而把影响对象行为的一个或多个动态变化的属性称为状态。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else
或 switch-case
语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else
语句,这违背了“开闭原则”,不利于程序的扩展。
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关 “判断逻辑” 提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的 状态类对象
进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else
、switch-case
等冗余语句。
我们现在来看一个例子:
现在城市发展很快,有两个东西在城市的发展过程中起到重要的作用,一个是汽车,另一个是电梯,因为汽车可以帮助城市横向扩展,电梯可以帮助城市纵向扩展。
今天我们就来聊聊电梯。
我们先来看看电梯都有哪几个状态:
- 敞门状态:按了电梯按钮就是敞门了,这个状态下只能关门;
- 闭门状态:电梯门关闭,这个状态下可以进行的动作是开门或者停止或者运行;
- 运行状态:电梯正在跑,在这个状态下电梯只能停止;
- 停止状态:电梯停止不动,这个状态下可以开门或者继续运行。
对应的状态机图画一下:
总共有四种状态,如果用 if-else
就要有 4 个分支,所以我们使用子类的方式,不使用分支结构。
创建四个子类,继承自一个状态的抽象类,该抽象类与环境类互相关联。
类图如下:
源码如下:
代码语言:javascript复制package com.lsu.state.practice;
/**
* 抽象电梯状态
*
* @Author wang suo
* @Date 2020/12/26 0026 13:56
* @Version 1.0
*/
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();
}
---
package com.lsu.state.practice;
/**
* 敞门状态
*
* @Author wang suo
* @Date 2020/12/26 0026 13:58
* @Version 1.0
*/
public class OpenningState extends LiftState {
/**
* 开启当然可以关闭了,我就想测试一下电梯门开关功能
*/
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.CLOSING_STATE);
//动作委托为CloseState来执行
super.context.getLiftState().close();
}
/**
* 打开电梯门
*/
@Override
public void open() {
System.out.println("电梯门开启...");
}
/**
* 门开着电梯就想跑,这电梯,吓死你!
*/
@Override
public void run() {
//do nothing;
}
/**
* 开门还不停止?
*/
@Override
public void stop() {
//do nothing;
}
}
---
package com.lsu.state.practice;
/**
* 关闭状态
*
* @Author wang suo
* @Date 2020/12/26 0026 13:59
* @Version 1.0
*/
public class ClosingState extends LiftState {
/**
* 电梯门关闭,这是关闭状态要实现的动作
*/
@Override
public void close() {
System.out.println("电梯门关闭...");
}
/**
* 电梯门关了再打开,逗你玩呢,那这个允许呀
*/
@Override
public void open() {
//置为门敞状态
super.context.setLiftState(Context.OPENNING_STATE);
super.context.getLiftState().open();
}
/**
* 电梯门关了就跑,这是再正常不过了
*/
@Override
public void run() {
//设置为运行状态;
super.context.setLiftState(Context.RUNNING_STATE);
super.context.getLiftState().run();
}
/**
* 电梯门关着,我就不按楼层
*/
@Override
public void stop() {
//设置为停止状态;
super.context.setLiftState(Context.STOPPING_STATE);
super.context.getLiftState().stop();
}
}
---
package com.lsu.state.practice;
/**
* 运行状态
*
* @Author wang suo
* @Date 2020/12/26 0026 14:00
* @Version 1.0
*/
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.STOPPING_STATE);
super.context.getLiftState().stop();
}
}
---
package com.lsu.state.practice;
/**
* 停止状态
*
* @Author wang suo
* @Date 2020/12/26 0026 14:01
* @Version 1.0
*/
public class StoppingState extends LiftState {
/**
* 停止状态关门?电梯门本来就是关着的!
*/
@Override
public void close() {
//do nothing;
}
/**
* 停止状态,开门,那是要的!
*/
@Override
public void open() {
super.context.setLiftState(Context.OPENNING_STATE);
super.context.getLiftState().open();
}
/**
* 停止状态再跑起来,正常的很
*/
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.getLiftState().run();
}
/**
* 停止状态是怎么发生的呢?当然是停止方法执行了
*/
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
---
package com.lsu.state.practice;
/**
* 环境类
*
* @Author wang suo
* @Date 2020/12/26 0026 13:57
* @Version 1.0
*/
public class Context {
/**
* 定义出所有的电梯状态
*/
public final static OpenningState OPENNING_STATE = new OpenningState();
public final static ClosingState CLOSING_STATE = new ClosingState();
public final static RunningState RUNNING_STATE = new RunningState();
public final static StoppingState STOPPING_STATE = 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)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
代码如下:
代码语言:javascript复制public abstract class State {
//定义一个环境角色,提供子类访问
protected Context context;
//设置环境角色
public void setContext(Context _context){
this.context = _context;
}
//行为1
public abstract void handle1();
//行为2
public abstract void handle2();
}
---
public class ConcreteState1 extends State {
@Override
public void handle1() {
//本状态下必须处理的逻辑
}
@Override
public void handle2() {
//设置当前状态为stat2
super.context.setCurrentState(Context.STATE2);
//过渡到state2状态,由Context实现
super.context.handle2();
}
}
---
public class ConcreteState2 extends State {
@Override
public void handle1() {
//设置当前状态为stat1
super.context.setCurrentState(Context.STATE1);
//过渡到state1状态,由Context实现
super.context.handle1();
}
@Override
public void handle2() {
//本状态下必须处理的逻辑
}
}
---
public class Context {
//定义状态
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
//当前状态
private State CurrentState;
//获得当前状态
public State getCurrentState() {
return CurrentState;
}
//设置当前状态
public void setCurrentState(State currentState) {
this.CurrentState = currentState;
//切换状态
this.CurrentState.setContext(this);
}
//行为委托
public void handle1(){
this.CurrentState.handle1();
}
public void handle2(){
this.CurrentState.handle2();
}
}
环境角色有两个不成文的约束:
- 把状态对象声明为
静态常量
,有几个状态对象就声明几个静态常量; - 环境角色具有状态抽象角色定义的所有行为。
状态模式的使用场景:
- 行为随状态改变而改变的场景;
- 条件、分支判断语句的替代者;
状态模式适用于当某个对象在他的状态发生改变时,它的行为也随着发生比较大的变化,在使用时对象的状态最好不要超过 5
个。