23种设计模式之状态模式
参考资料
- Java设计模式:23种设计模式全面解析(超级详细)
- 韩顺平老师的Java设计模式(图解+框架源码剖析)
- 秦小波老师的《设计模式之禅》
下文如有错漏之处,敬请指正
一、简介
定义
允许一个对象在其内部状态发生改变时改变其行为。
特点
- 状态模式是一种行为型模式。
- 状态模式把一个对象因受环境改变而改变的行为包装在不同的状态对象里。
通用类图
状态模式的主要角色:
-
Context
环境角色
定义客户端需要的接口,并且负责具体状态的切换。
-
State
抽象状态角色
接口或抽象类,负责对象状态的定义,并且封装环境角色以实现状态切换。
-
ConcreteState
具体状态角色
实现抽象状态角色所对应的行为,负责本状态的行为管理以及本状态如何过渡到其他状态。
优点
-
结构清晰
避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高系统的可维护性。
-
遵循设计原则
很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
-
封装性非常好
这也是状态模式的基本要求,状态和其行为的变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换,减少对象间的相互依赖。
缺点
- 如果子类太多,会类膨胀;且如果状态模式的结构与实现都较为复杂,使用不当会导致程序结构和代码的混乱。其实有很多方式可以解决这个状态问题,如在数据库中建立一个状态表,然后根据状态执行相应的操作。
应用场景
-
行为随状态改变而改变的场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。如域名备案(上传材料——腾讯云审核——提交管局——管局审核)
-
条件、分支判断语句的替代者
在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理。
二、状态模式
需求:
现在要使用某个APP进行抽奖,抽奖状态有:抽奖验证、正在抽奖、奖品领取、抽奖结束;使用状态模式模拟这一功能。
Context:
package state;
// 抽奖活动
public class Context {
// 定义状态
public static State raffleCheckState = new RaffleCheckState();// 抽奖验证状态
public static State raffleDoingState = new RaffleDoingState();// 正在抽奖状态
public static State grantAwardsState = new GrantAwardsState();// 领取奖品状态
public static State raffleEndState = new RaffleEndState(); // 抽奖结束状态
// 当前状态
private State state;
// 奖品的数量
private int count;
// 初始化奖品数量
public Context(int count) {
this.count = count;
}
// 设置当前状态
public void setState(State state) {
this.state = state;
// 切换状态
this.state.setContext(this);
}
public int getCount() {
return count;
}
public void deCount() {
--this.count;
}
// ===========委托行为===========
// 检查奖品数量行为
public void checkAwardCount() {
this.state.checkAwardCount();
}
// 抽奖行为
public void raffle() {
this.state.raffle();
}
// 领取奖品行为
public void grantAwards() {
this.state.grantAwards();
}
}
State:
package state;
public abstract class State {
// 定义一个环境角色,提供子类访问
protected Context context;
// 设置环境角色
public void setContext(Context context) {
this.context = context;
}
// ===========行为===========
// 检查奖品数量行为
public abstract void checkAwardCount();
// 抽奖行为
public abstract void raffle();
// 发放奖品行为
public abstract void grantAwards();
}
ConcreteState:
package state;
// 抽奖验证状态
public class RaffleCheckState extends State {
@Override
public void checkAwardCount() {
if (super.context.getCount() == 0) {
// 没有奖品了,设置状态变为抽奖结束状态
super.context.setState(Context.raffleEndState);
} else {
// 有奖品,设置状态变为正在抽奖状态
super.context.setState(Context.raffleDoingState);
}
// 过渡到检查奖品数量行为
super.context.raffle();
}
@Override
public void raffle() {
this.checkAwardCount();
}
@Override
public void grantAwards() {
}
}
package state;
import java.util.Random;
// 正在抽奖状态
public class RaffleDoingState extends State {
@Override
public void checkAwardCount() {
}
@Override
public void raffle() {
System.out.println("正在抽奖中……");
Random random = new Random();
// 随机抽奖编号
int num = random.nextInt(10);
// 默认中奖编号为7
if (num == 7) {
// 抽中了奖品,设置状态为领取奖品状态
super.context.setState(Context.grantAwardsState);
// 过渡到领取奖品行为
super.context.grantAwards();
} else {
System.out.println("很遗憾你没有抽中奖品");
// 设置状态为抽奖验证状态
super.context.setState(Context.raffleCheckState);
}
}
@Override
public void grantAwards() {
}
}
package state;
// 奖品领取状态
public class GrantAwardsState extends State {
@Override
public void checkAwardCount() {
}
@Override
public void raffle() {
}
@Override
public void grantAwards() {
System.out.println("==恭喜你中奖了!请领取你的奖品==");
// 将奖品数量-1
super.context.deCount();
// 设置状态变为状态
super.context.setState(Context.raffleCheckState);
}
}
package state;
// 抽奖结束状态
public class RaffleEndState extends State {
@Override
public void checkAwardCount() {
}
@Override
public void raffle() {
System.out.println("奖品已发放完毕,请期待下次的抽奖活动!");
// 将状态变为抽奖结束状态
System.out.println("============================================");
}
@Override
public void grantAwards() {
}
}
Client:
package state;
public class Client {
public static void main(String[] args) {
// 创建抽奖活动,共有3个奖品
Context context = new Context(3);
// 初始化状态
context.setState(new RaffleCheckState());
// 模拟用户抽奖100次
for (int i = 0; i < 100; i++) {
System.out.println("第" + (i + 1) + "次抽奖");
// 模拟用户点击抽奖
context.raffle();
}
/**
* 输出结果:
* 第1次抽奖
* 正在抽奖中……
* 很遗憾你没有抽中奖品
* 第2次抽奖
* 正在抽奖中……
* 很遗憾你没有抽中奖品
* 第3次抽奖
* 正在抽奖中……
* 很遗憾你没有抽中奖品
* --------------------此处省略-----------------
* 第15次抽奖
* 正在抽奖中……
* ==恭喜你中奖了!请领取你的奖品==
* --------------------此处省略-----------------
* 第22次抽奖
* 正在抽奖中……
* ==恭喜你中奖了!请领取你的奖品==
* --------------------此处省略-----------------
* 第28次抽奖
* 正在抽奖中……
* ==恭喜你中奖了!请领取你的奖品==
* 第29次抽奖
* 奖品已发放完毕,请期待下次的抽奖活动!
* ============================================
* --------------------此处省略-----------------
* 第100次抽奖
* 奖品已发放完毕,请期待下次的抽奖活动!
* ============================================
*/
}
}
当需要新增一个状态时,只需要增加一个子类(继承State),在Context环境中添加对应的状态即可。
三、总结
状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个,不然就会导致类膨胀,不易于维护。