目录
状态模式【State Pattern】,什么是状态模式?主要角色?主要作用?优缺点?适用场景?状态模式实现案例?
什么是状态模式?
状态模式(State Pattern)是一种行为设计模式,允许一个对象在其内部状态改变时改变其行为。状态模式将一个对象的状态和与之相关的行为分离开来,使得对象在不同状态下可以有不同的行为表现,从而使得代码更加清晰和易于维护。
状态模式主要角色
(1)Context(上下文)
维护一个当前状态的实例,并定义一个接口,允许客户端通过这个接口来变更状态。
(2)State(状态)
定义一个接口,用于封装与上下文的一个特定状态相关的行为。
(3)ConcreteState(具体状态)
每个具体状态实现状态接口,定义在这个状态下具体的行为。
状态模式的主要作用
(1)将状态逻辑集中管理
将状态相关的行为集中在状态类中,使得状态的添加和修改更加方便。
(2)避免使用大量的条件判断
通过将状态和行为封装到独立的状态类中,减少了复杂的条件判断。
(3)增加系统的灵活性
状态模式使得系统在运行时可以方便地切换状态,从而改变对象的行为。
状态模式的优缺点
优点
(1)清晰的结构
将状态和行为分离,使得系统的结构更加清晰。
(2)易于扩展
增加新的状态只需要增加一个新的状态类,而不需要修改现有代码。
(3)减少条件判断
通过状态模式避免了在上下文类中使用大量的条件判断,使代码更加简洁。
缺点
(1)类数量增加
每个状态都需要一个独立的类,这会导致类的数量增加。
(2)复杂性
在一些简单的应用场景下,状态模式可能会引入不必要的复杂性。
状态模式适用场景
(1)状态行为变化复杂的对象
如:状态机、订单处理系统等。
(2)行为依赖于状态的对象
如:在工作流系统中,根据不同的流程阶段改变行为。
(3)需要在运行时动态改变行为的对象
如:文档编辑器中不同的编辑状态。
状态模式实现案例
我们每天都在乘电梯,那来看看电梯有哪些动作(映射到 Java 中就是有多少方法):开门、关门、
运行、停止,就这四个动作。好,就用程序来实现一下电梯的动作。
先看类图设计:
电梯门可以打开,但不是随时都可以开,是有前提条件的,不可能电梯在运行的时候突然开门吧?电梯也不会出现停止了但是不开门的情况吧!那要是有也是事故嘛,再仔细想想,电梯的这四个动作的执行都是有前置条件;具体点,是在特定状态下才能做特定事,那来分析一下电梯有什么那些特定状态:
门敞状态—按了电梯上下按钮,电梯门开,这中间有 5 秒的时间(当然你也可以用身体挡住电梯门,那就不是 5 秒了),那就是门敞状态;在这个状态下电梯只能做的动作是关门动作,做别的动作?那就危险喽
门闭状态—电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了)、停止(忘记按路层号了)、运行
运行状态—电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止;
停止状态—电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作;
用一张表来表示电梯状态和动作之间的关系:
电梯状态和动作对应表(×表示不允许,√表示允许动作)
开门(open) | 关门(close) | 运行(run) | 停止(stop) | |
---|---|---|---|---|
门敞状态 | × | √ | × | × |
门闭状态 | √ | × | √ | √ |
运行状态 | × | × | × | √ |
停止状态 | √ | × | √ | × |
1、定义上下文
package com.uhhe.common.design.state;
/**
* 上下文
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:56
*/
public class Context {
/**
* 定义出所有的电梯状态
*/
public final static OpeningState OPENING_STATE = new OpeningState(); // 门敞状态
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();
}
}
2、状态抽象类(State)
package com.uhhe.common.design.state;
/**
* 状态抽象
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:54
*/
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();
}
3、状态实现类(ConcreteState,门敞状态、门闭状态、运行状态、停止状态)
门敞状态
package com.uhhe.common.design.state;
/**
* 门敞状态:在电梯门开启的状态下能做什么事情
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:55
*/
public class OpeningState 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() {
// 门开着电梯就想跑,这电梯,吓死你!
}
@Override
public void stop() {
// 开门还不停止?
}
}
门闭状态
package com.uhhe.common.design.state;
/**
* 门闭状态:电梯门关闭以后,电梯可以做哪些事情
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:57
*/
public class ClosingState extends LiftState {
@Override
public void close() {
// 电梯门关闭,这是关闭状态要实现的动作
System.out.println("电梯门关闭...");
}
@Override
public void open() {
// 电梯门关了再打开,逗你玩呢,那这个允许呀
super.context.setLiftState(Context.OPENING_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.uhhe.common.design.state;
/**
* 运行状态:电梯在运行状态下能做哪些动作
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:58
*/
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.uhhe.common.design.state;
/**
* 停止状态:在停止状态下能做什么事情
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 19:59
*/
public class StoppingState extends LiftState {
@Override
public void close() {
// 停止状态关门?电梯门本来就是关着的!
// do nothing;
}
@Override
public void open() {
// 停止状态,开门,那是要的!
super.context.setLiftState(Context.OPENING_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("电梯停止了...");
}
}
4、模拟电梯的动作
package com.uhhe.common.design.state;
/**
* 模拟电梯的动作
*
* @author nizhihao
* @version 1.0.0
* @date 2023/3/2 20:01
*/
public class Client {
/**
* 状态模式【State Pattern】
* <p>
* 什么是状态模式呢?
* 当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。
* 也就是说状态模式封装的非常好,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
* <p>
* 状态模式中有什么优点呢?
* 首先是避免了过多的 swith/case 或者 if..else 语句的使用,避免了程序的复杂性;其次是很好的使用体现了开闭原则和单一职责原则,
* 每个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了;最后一个好处就是封装性非常好,这也是状态模式的基本要求,
* 状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
* <p>
* 状态模式只有一个缺点,子类会太多,也就是类膨胀,你想一个事物有七八、十来个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理,
* 这个需要大家在项目自己衡量。其实有很大方式解决这个状态问题
* 比如: 在数据库中建立一个状态表,然后根据状态执行相应的操作,这个也不复杂,看大家的习惯和嗜好了。
* <p>
* 状态模式使用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说行为是受状态约束的情况下可以使用状态模式,
* 而且状态模式使用时对象的状态最好不要超过五个,防止你写子类写疯掉。
*/
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}