文章目录
- 1 状态模式(State Pattern)
- 1.1 介绍
- 1.2 概述
- 1.3 状态模式的结构
- 1.4 状态模式的优缺点
- 1.5 状态模式的使用场景
- 2 案例一
- 2.1 需求
- 2.2 代码实现(未使用状态模式)
- 2.3 代码实现(状态模式)
- 3 案例二
- 3.1 需求
- 3.2 代码实现
🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的状态模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》
本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》
⬇️本系列 - 创建型模式 - 链接🔗单例模式:《瑞_23种设计模式_单例模式》
工厂模式:《瑞_23种设计模式_工厂模式》
原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
建造者模式:《瑞_23种设计模式_建造者模式》⬇️本系列 - 结构型模式 - 链接🔗
代理模式:《瑞_23种设计模式_代理模式》
适配器模式:《瑞_23种设计模式_适配器模式》
装饰者模式:《瑞_23种设计模式_装饰者模式》
桥接模式:《瑞_23种设计模式_桥接模式》
外观模式:《瑞_23种设计模式_外观模式》
组合模式:《瑞_23种设计模式_组合模式》
享元模式:《瑞_23种设计模式_享元模式》⬇️本系列 - 行为型模式 - 链接🔗
模板方法模式:《瑞_23种设计模式_模板方法模式》
策略模式:《瑞_23种设计模式_策略模式》
命令模式:《瑞_23种设计模式_命令模式》
职责链模式:《瑞_23种设计模式_职责链模式》
状态模式:《后续更新》
观察者模式:《后续更新》
中介者模式:《后续更新》
迭代器模式:《后续更新》
访问者模式:《后续更新》
备忘录模式:《后续更新》
解释器模式:《后续更新》
1 状态模式(State Pattern)
瑞:状态模式堆程序员水平要求较高,使用有点困难,属于提升模式。可用于简化复杂的条件语句(如一个操作中含有庞大的分支结构,即长的 if-else 链或 switch case),并且这些分支决定于对象的状态。
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
瑞:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
瑞:行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
状态模式属于:对象行为模式
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
1.1 介绍
-
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
-
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
-
何时使用:代码中包含大量与对象状态有关的条件语句。
-
如何解决:将各种具体的状态类抽象出来。
-
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和策略模式一样,也可以用于消除 if…else 等条件选择语句。
-
应用实例:
1️⃣ 打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
2️⃣ 曾侯乙编钟中,“钟是抽象接口”,“钟A”等是具体状态,“曾侯乙编钟”是具体环境(Context)。 -
优点:
1️⃣ 封装了转换规则。
2️⃣枚举可能的状态,在枚举状态之前需要确定状态种类。
3️⃣ 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
4️⃣ 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
5️⃣ 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 -
缺点:
1️⃣ 状态模式的使用必然会增加系统类和对象的个数。
2️⃣ 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3️⃣ 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。 -
使用场景:
1️⃣ 行为随状态改变而改变的场景。
2️⃣ 条件、分支语句的代替者。 -
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
1.2 概述
定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式通过将每个状态的行为封装在各自的类中,使得对象可以在运行时根据其内部状态的变化而改变行为,从而提供了一种优雅的方式来组织和管理复杂的状态转换逻辑。
使用状态模式的好处是可以简化复杂的条件语句(如长的 if-else 链),使得代码更加清晰和易于维护。此外,当新的状态需要添加到系统中时,只需要增加一个新的具体状态类,而不需要修改环境类或其他状态类,这有助于保持系统的灵活性和可扩展性。
1.3 状态模式的结构
- 状态模式主要包含以下角色:
1️⃣ 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
2️⃣ 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
3️⃣ 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
1.4 状态模式的优缺点
优点:
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好。
1.5 状态模式的使用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
2 案例一
【案例】电梯状态控制
2.1 需求
通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
该案例未使用状态模式设计的类图如下:
2.2 代码实现(未使用状态模式)
/**
* 电梯接口
*
* @author LiaoYuXing-Ray
**/
public interface ILift {
// 定义四个电梯状态的常量
int OPENING_STATE = 1;
int CLOSING_STATE = 2;
int RUNNING_STATE = 3;
int STOPPING_STATE = 4;
// 设置电梯状态的功能
void setState(int state);
// 电梯操作功能
void open();
void close();
void run();
void stop();
}
/**
* 电梯类(ILift的子实现类)
*
* @author LiaoYuXing-Ray
**/
public class Lift implements ILift {
// 声明一个记录当前电梯的状态
private int state;
public void setState(int state) {
this.state = state;
}
public void open() {
switch (state) { // 当前电梯状态
case OPENING_STATE :
// 什么事都不做
break;
case CLOSING_STATE :
System.out.println("电梯打开了...");
// 设置当前电梯状态为开启状态
setState(OPENING_STATE);
break;
case STOPPING_STATE :
System.out.println("电梯打开了...");
// 设置当前电梯状态为开启状态
setState(OPENING_STATE);
break;
case RUNNING_STATE :
// 什么事都不做
break;
}
}
public void close() {
switch (this.state) {
case OPENING_STATE:
System.out.println("电梯关门了。。。"); // 只有开门状态可以关闭电梯门,可以对应电梯状态表来看
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 run() {
switch (this.state) {
case OPENING_STATE: // 电梯不能开着门就走
// do nothing
break;
case CLOSING_STATE: // 门关了,可以运行了
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE); // 现在是运行状态
break;
case RUNNING_STATE:
// do nothing 已经是运行状态了
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);
break;
}
}
public void stop() {
switch (this.state) {
case OPENING_STATE: // 开门的电梯已经是是停止的了(正常情况下)
// do nothing
break;
case CLOSING_STATE: // 关门时才可以停止
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case RUNNING_STATE: // 运行时当然可以停止了
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case STOPPING_STATE:
// do nothing
break;
}
}
}
/**
* 测试类
*
* @author LiaoYuXing-Ray
**/
public class Client {
public static void main(String[] args) {
// 创建电梯对象
Lift lift = new Lift();
/*
设置当前电梯的状态
*/
lift.setState(ILift.OPENING_STATE);
lift.open();
lift.close();
lift.run();
lift.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
lift.setState(ILift.CLOSING_STATE);
lift.open();
lift.close();
lift.run();
lift.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
lift.setState(ILift.RUNNING_STATE);
lift.open();
lift.close();
lift.run();
lift.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
lift.setState(ILift.STOPPING_STATE);
lift.open();
lift.close();
lift.run();
lift.stop();
}
}
代码运行结果如下:
电梯关门了。。。
电梯开始运行了。。。
电梯停止了。。。
=== 我是一条华丽的分割线 ===
电梯打开了...
电梯关门了。。。
电梯开始运行了。。。
电梯停止了。。。
=== 我是一条华丽的分割线 ===
电梯停止了。。。
=== 我是一条华丽的分割线 ===
电梯打开了...
电梯关门了。。。
电梯开始运行了。。。
电梯停止了。。。
问题分析:
- 使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
- 扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑
2.3 代码实现(状态模式)
对上述电梯的案例使用状态模式进行改进。类图如下:
/**
* 抽象状态类
*
* @author LiaoYuXing-Ray
**/
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();
}
/**
* 电梯开启状态类
*
* @author LiaoYuXing-Ray
**/
public class OpeningState extends LiftState {
// 当前状态要执行的方法
public void open() {
System.out.println("电梯开启。。。");
}
public void close() {
// 修改状态
super.context.setLiftState(Context.CLOSING_STATE);
// 调用当前状态中的context中的close方法
super.context.close();
}
public void run() {
// 什么都不做
}
public void stop() {
// 什么都不做
}
}
/**
* 电梯运行状态类
*
* @author LiaoYuXing-Ray
**/
public class RunningState extends LiftState {
// 运行的时候开电梯门?你疯了!电梯不会给你开的
@Override
public void open() {
// do nothing
}
// 电梯门关闭?这是肯定了
@Override
public void close() {// 虽然可以关门,但这个动作不归我执行
// do nothing
}
// 这是在运行状态下要实现的方法
@Override
public void run() {
System.out.println("电梯正在运行...");
}
// 这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
/**
* 电梯停止状态类
*
* @author LiaoYuXing-Ray
**/
public class StoppingState extends LiftState {
// 停止状态,开门,那是要的!
@Override
public void open() {
// 状态修改
super.context.setLiftState(Context.OPENING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().open();
}
// 虽然可以关门,但这个动作不归我执行
@Override
public void close() {
// 状态修改
super.context.setLiftState(Context.CLOSING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}
// 停止状态再跑起来,正常的很
@Override
public void run() {
// 状态修改
super.context.setLiftState(Context.RUNNING_STATE);
// 动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().run();
}
// 停止状态是怎么发生的呢?当然是停止方法执行了
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
/**
* 电梯关闭状态类
*
* @author LiaoYuXing-Ray
**/
public class ClosingState extends LiftState {
// 电梯门关闭,这是关闭状态要实现的动作
@Override
public void close() {
System.out.println("电梯门关闭...");
}
// 电梯门关了再打开
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open();
}
// 电梯门关了就跑,这是再正常不过了
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run();
}
// 电梯门关着,我就不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
/**
* 环境角色类
*
* @author LiaoYuXing-Ray
**/
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;
// 设置当前状态对象中的Context对象
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();
}
}
/**
* 测试类
*
* @author LiaoYuXing-Ray
**/
public class Client {
public static void main(String[] args) {
// 创建环境角色对象
Context context = new Context();
/*
设置当前电梯装填
*/
context.setLiftState(new OpeningState());
context.open();
context.run();
context.close();
context.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
context.setLiftState(new RunningState());
context.open();
context.run();
context.close();
context.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
context.setLiftState(new StoppingState());
context.open();
context.run();
context.close();
context.stop();
System.out.println("\n=== 我是一条华丽的分割线 ===\n");
context.setLiftState(new ClosingState());
context.open();
context.run();
context.close();
context.stop();
}
}
代码运行结果如下:
电梯开启。。。
电梯门关闭...
电梯停止了...
=== 我是一条华丽的分割线 ===
电梯正在运行...
电梯停止了...
=== 我是一条华丽的分割线 ===
电梯开启。。。
电梯门关闭...
电梯停止了...
=== 我是一条华丽的分割线 ===
电梯开启。。。
电梯门关闭...
电梯停止了...
3 案例二
本案例为菜鸟教程中的案例
3.1 需求
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
3.2 代码实现
步骤 1
创建一个接口。
public interface State {
public void doAction(Context context);
}
步骤 2
创建实现接口的实体类。
public class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
public class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
步骤 3
创建 Context 类。
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
步骤 4
使用 Context 来查看当状态 State 改变时的行为变化。
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
步骤 5
执行程序,输出结果:
Player is in start state
Start State
Player is in stop state
Stop State
如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~