状态模式
定义
有限状态机
状态存储关于过去的信息,就是说:它反映从系统开始到现在时刻的输入变化。转移指示状态变更,并且用必须满足确使转移发生的条件来描述它。动作是在给定时刻要进行的活动的描述。有多种类型的动作:
- 进入动作(entry action):在进入状态时进行
- 退出动作(exit action):在退出状态时进行
- 输入动作:依赖于当前状态和输入条件进行
- 转移动作:在进行特定转移时进行
FSM(有限状态机)可以使用上面图1那样的状态图(或状态转移图)来表示。此外可以使用多种类型的状态转移表。下面展示最常见的表示:当前状态(B)和条件(Y)的组合指示出下一个状态(C)。完整的动作信息可以只使用脚注来增加。包括完整动作信息的FSM定义可以使用状态表。
状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
每个状态的行为局部化到它自己的类中,将容易产生问题的if语句删除、以便日后的维护。让每一个状态" 对修改关闭" 让糖果机对外开放,因为可以加入新的状态类。创建一个新的代码基和类结构。
状态机实现方式
1. 分支逻辑法
简单的说,就是在类中嵌套多重if/else
判断语句。对于简单的状态机来说,分支实现是可以接受的。但是,对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转换。而且可读性、可维护性都很差。
2. 查表法
实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示。相对于分支逻辑判断,查表法
的代码实现更加清晰,可读性、可维护性更好。但只能支持简单的操作。如果执行一系列复杂的操作,我们就没有办法继续使用如此简单的二维数组来表示了。因此有一定局限性。
3. 状态模式
状态模式
通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。
类图
实现
1. 定义状态接口
public interface State {
/**
* 投币动作
*/
void insertQuarter();
/**
* 退回动作
*/
void ejectQuarter();
/**
* 转动曲柄动作
*/
void trunCrank();
/**
* 发放糖果
*/
void dispense();
}
2. 定义各种状态实现类
- 无币
public class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("塞入25分钱");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("别这么厚脸皮,根本就没有投币");
}
@Override
public void trunCrank() {
System.out.println("别这么厚脸皮,根本就没有投币,也就不能获取糖果");
}
@Override
public void dispense() {
System.out.println("别这么厚脸皮,根本就没有投币");
}
@Override
public String toString() {
return "未投币";
}
}
- 有币
public class HasQuarterState implements State{
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("已经塞入25分钱,请勿重复塞入钱币");
}
@Override
public void ejectQuarter() {
System.out.println("钱币已退回");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void trunCrank() {
System.out.println("转动曲柄,即将发放糖果");
gumballMachine.setState(gumballMachine.getSoldState());
}
@Override
public void dispense() {
System.out.println("请先转动曲柄,才能发放糖果");
}
@Override
public String toString() {
return "已投币";
}
}
- 出货中
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("糖果正在发放,请勿投币");
}
@Override
public void ejectQuarter() {
System.out.println("你都已经转动曲柄了,没有后悔药了");
}
@Override
public void trunCrank() {
System.out.println("不要重复转动曲柄");
}
@Override
public void dispense() {
gumballMachine.releaesBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
System.out.println("本次糖果已发放");
} else {
System.out.println("Ops, 本机器已没有糖果可给大家了");
gumballMachine.setState(gumballMachine.soldOutState);
}
}
@Override
public String toString() {
return "发送中";
}
}
- 售完
public class SoldOutState implements State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("投币成功");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("当前无币,请投币");
}
@Override
public void trunCrank() {
System.out.println("当前无币,请投币");
}
@Override
public void dispense() {
System.out.println("当前无币,请投币");
}
@Override
public String toString() {
return "已发送";
}
}
2. 定义Context
@Data
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state;
int count = 0;
public GumballMachine(int count) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
state = noQuarterState;
this.count = count;
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.trunCrank();
state.dispense();
}
public void releaesBall() {
if (count != 0) {
count -=1;
}
}
@Override
public String toString() {
return "当前糖果机器状态: " + state + ", 剩余糖果数: " + count;
}
}
3. Main方法
public class M {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(2);
System.out.println(gumballMachine);
// 测试非法操作
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();
// 正常流程
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
总结
-
状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 -
对于状态不多的、状态转移比较简单、但事件触发执行的动作包含的业务逻辑比较复杂的状态机来说,首选使用状态模式实现。
-
状态模式
VS策略模式
- 策略模式: 客户通常主动指定Context所要组合的对象是哪一个。一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很困难。有了策略模式,你可以组合不同的对象来改变行为。
- 我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。在context可以决定状态转换的流向。一般来说,当状态转换是固定的时候,就适合放在context中。然而,当转换是更动态的时候,通常就会放在状态类中。缺点是: 状态类之间产生了耦合。