状态模式(State Pattern)学习笔记
1.为什么要使用状态设计模式`
如果一个对象总是处于几个已知状态中,并且当它处于不同的状态时,方法对应的行为不同。这时候使用状态设计模式较为合适。
对比使用if/else语句来解决这个问题,使用状态设计模式可以便于程序将来的设计和修改。
状态设计模式允许在运行时更改对象的行为或状态。每个行为都由一个状态类表示,并且该行为可以从一个状态更改为另一个状态。
2.如何尝试使用状态设计模式
大致思路是先写一个State接口,其他的状态都来实现这个接口。
关于这个State接口,好像也有些许的不同。
课上讲的方案是方案A
// 状态接口(方案A)
public interface State{
State move(char c);
boolean accept();
}
习题课中使用的方法是方案B
//状态接口(方案B)
public interface State {
State parking(); //停车
State depart(); //驶离
}
这两种方案有何不同呢
我个人感觉第二种方法比较直观,因为一看到方法的名字就可以知道它会转移到什么状态。在后续的操作中也可以直接调用对应的方法。相比于此,方法一就有一个明显的缺点,要转移到不同的状态就需要输入不同的字符型变量,显然不如第一个方法直观,可能会更容易出现拼写错误,程序员阅读起来可能也较为困难,需要更多的注释来补充帮助阅读。
我个人是倾向于在编程中使用第二种方法的,因为它看起来很直观,编写的时候思路会很清晰(比较适合经常写着写着就懵了的我…)。但是第二种方法也有它的缺点,例如:在状态较多时,由于每一个状态都要对应一个转移的方法,就会产生很多方法,但是实际上在状态中能调用的方法只有几个,很多方法的处理是抛出IllegalArgumentException,代码会很不美(可能一看就是菜鸟程序员)。而且这种做法可能违反LSP原则。
不过在Lab3的实验中我还是使用了方案二(本来就是菜鸟程序员),Lab3的内容是开发一套可复用的ADT实现。其中的状态转换图包括下面两个:
这两个状态转移图的区别是,上方的状态转移图是可阻塞(block)的,而下面的状态转移图是不可阻塞的。
因此State的接口就应该包括以下这些方法:
①allocate(从waiting转移到allocated)
②startup(从allocated/blocked转移到running)
③cancel(从waiting/allocated/blocked转移到cancelled)
④block(从runing转移到block)
⑤complete(从running转移到ended)
因此State接口:
public interface State {
State allocate();
State startup();
State cancel();
State complete();
State block();
}
按照前面的思路,它的具体实现也就很简单了,以WAITING状态为例
public class WAITING implements State{
static State instance = new WAITING();
private WAITING() {};
@Override
public State allocate() {
return ALLOCATED.instance;
}
@Override
public State startup() {
throw new IllegalArgumentException();
}
@Override
public State cancel() {
return CANCELLED.instance;
}
@Override
public State complete() {
throw new IllegalArgumentException();
}
@Override
public State block() {
throw new IllegalArgumentException();
}
public static State getState() {
return instance;
}
@Override
public String toString() {
return "WAITING";
}
}
具体使用时,只需要用try-catch语句就可以完成状态的转移
private State state = WAITING.getState();
public boolean startup() {
try{
state = state.startup();
return true;
}catch(IllegalArgumentException IAE) {
return false;
}
}
至此就完成了状态模式的初次使用。