原文地址:服務(wù)端指南 | 狀態(tài)機設(shè)計
博客地址:http://blog.720ui.com/
狀態(tài)機中,每個狀態(tài)有著相應(yīng)的行為,隨著行為的觸發(fā)來切換狀態(tài)。其中一種做法是使用二維數(shù)組實現(xiàn)狀態(tài)機機制疑务,其中橫坐標(biāo)表示行為,縱坐標(biāo)表示狀態(tài)梗醇,具體的數(shù)值則表示當(dāng)前的狀態(tài)知允。
我們以登錄場景設(shè)計一個狀態(tài)機。
這時叙谨,我們設(shè)計一張狀態(tài)機表温鸽。
那么,此時它的二維數(shù)組手负,如下所示涤垫。
此外,我們也可以通過狀態(tài)模式實現(xiàn)一個狀態(tài)機竟终。狀態(tài)模式將每一個狀態(tài)封裝成獨立的類蝠猬,具體行為會隨著內(nèi)部狀態(tài)而改變。狀態(tài)模式用類表示狀態(tài)衡楞,這樣我們就能通過切換類來方便地改變對象的狀態(tài)吱雏,避免了冗長的條件分支語句,讓系統(tǒng)具有更好的靈活性和可擴展性瘾境。
現(xiàn)在歧杏,我們定義一個狀態(tài)枚舉,其中包括未連接迷守、已連接犬绒、注冊中、已注冊 4 種狀態(tài)兑凿。
public enum StateEnum {
// 未連接
UNCONNECT(1, "UNCONNECT"),
// 已連接
CONNECT(2, "CONNECT"),
// 注冊中
REGISTING(3, "REGISTING"),
// 已注冊
REGISTED(4, "REGISTED");
private int key;
private String value;
StateEnum(int key, String value) {
this.key = key;
this.value = value;
}
public int getKey() {return key;}
public String getValue() {return value;}
}
定義一個環(huán)境類凯力,它是實際上是真正擁有狀態(tài)的對象。
public class Context {
private State state;
public void connect(){
state.connect(this);
System.out.println("STATE : " + state.getCurState());
}
public void register(){
state.register(this);
System.out.println("STATE : " + state.getCurState());
}
public void registerSuccess(){
state.registerSuccess(this);
System.out.println("STATE : " + state.getCurState());
}
public void registerFailed(){
state.registerFailed(this);
System.out.println("STATE : " + state.getCurState());
}
public void unRegister(){
state.unRegister(this);
System.out.println("STATE : " + state.getCurState());
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
狀態(tài)模式用類表示狀態(tài)礼华,這樣我們就能通過切換類來方便地改變對象的狀態(tài)「篮祝現(xiàn)在,我們定義幾個狀態(tài)類圣絮。
public interface State {
void connect(Context c);
void register(Context c);
void registerSuccess(Context c);
void registerFailed(Context c);
void unRegister(Context c);
String getCurState();
}
public class UnconnectState implements State {
@Override
public void connect(Context c) {
c.setState(new ConnectState());
}
@Override
public void register(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void registerSuccess(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void registerFailed(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void unRegister(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public String getCurState() {
return StateEnum.UNCONNECT.toString();
}
}
public class ConnectState implements State {
@Override
public void connect(Context c) {
c.setState(new ConnectState());
}
@Override
public void register(Context c) {
c.setState(new RegistingState());
}
@Override
public void registerSuccess(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void registerFailed(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void unRegister(Context c) {
c.setState(new UnconnectState());
}
@Override
public String getCurState() {
return StateEnum.CONNECT.toString();
}
}
public class RegistingState implements State {
@Override
public void connect(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void register(Context c) {
c.setState(new RegistingState());
}
@Override
public void registerSuccess(Context c) {
c.setState(new RegistedState());
}
@Override
public void registerFailed(Context c) {
c.setState(new UnconnectState());
}
@Override
public void unRegister(Context c) {
c.setState(new UnconnectState());
}
@Override
public String getCurState() {
return StateEnum.REGISTING.toString();
}
}
public class RegistedState implements State {
@Override
public void connect(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void register(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void registerSuccess(Context c) {
c.setState(new RegistedState());
}
@Override
public void registerFailed(Context c) {
throw new RuntimeException("INVALID_OPERATE_ERROR");
}
@Override
public void unRegister(Context c) {
c.setState(new UnconnectState());
}
@Override
public String getCurState() {
return StateEnum.REGISTED.toString();
}
}
注意的是祈惶,如果某個行為不會觸發(fā)狀態(tài)的變化,我們可以拋出一個 RuntimeException 異常。此外捧请,調(diào)用時凡涩,通過環(huán)境類控制狀態(tài)的切換,如下所示疹蛉。
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.connect();
context.register();
}
}
Spring StateMachine 讓狀態(tài)機結(jié)構(gòu)更加層次化活箕,可以幫助開發(fā)者簡化狀態(tài)機的開發(fā)過程。現(xiàn)在可款,我們來用 Spring StateMachine 進(jìn)行改造育韩。修改 pom 文件,添加 Maven 依賴闺鲸。
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
定義一個狀態(tài)枚舉座慰,其中包括未連接、已連接翠拣、注冊中版仔、已注冊 4 種狀態(tài)。
public enum RegStatusEnum {
// 未連接
UNCONNECTED,
// 已連接
CONNECTED,
// 注冊中
REGISTERING,
// 已注冊
REGISTERED;
}
定義一個行為枚舉误墓,其中包括連接蛮粮、注冊、注冊成功谜慌、注冊失敗然想、注銷 5 種行為事件。
public enum RegEventEnum {
// 連接
CONNECT,
// 注冊
REGISTER,
// 注冊成功
REGISTER_SUCCESS,
// 注冊失敗
REGISTER_FAILED,
// 注銷
UN_REGISTER;
}
接著欣范,我們需要進(jìn)行狀態(tài)機配置变泄,其中 @EnableStateMachine 注解,標(biāo)識啟用 Spring StateMachine 狀態(tài)機功能恼琼。
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
}
我們需要初始化狀態(tài)機的狀態(tài)妨蛹。其中,initial(RegStatusEnum.UNCONNECTED) 定義了初始狀態(tài)是未連接狀態(tài)晴竞。states(EnumSet.allOf(RegStatusEnum.class)) 定義了狀態(tài)機中存在的所有狀態(tài)蛙卤。
@Override
public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
states.withStates()
// 定義初始狀態(tài)
.initial(RegStatusEnum.UNCONNECTED)
// 定義狀態(tài)機狀態(tài)
.states(EnumSet.allOf(RegStatusEnum.class));
}
我們需要初始化當(dāng)前狀態(tài)機有哪些狀態(tài)事件。其中噩死, source 指定原始狀態(tài)颤难,target 指定目標(biāo)狀態(tài),event 指定觸發(fā)事件已维。
@Override
public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions)
throws Exception {
// 1.連接事件
// 未連接 -> 已連接
.withExternal()
.source(RegStatusEnum.UNCONNECTED)
.target(RegStatusEnum.CONNECTED)
.event(RegEventEnum.CONNECT)
.and()
.withExternal()
.source(RegStatusEnum.CONNECTED)
.target(RegStatusEnum.CONNECTED)
.event(RegEventEnum.CONNECT)
.and()
// 2.注冊事件
// 已連接 -> 注冊中
.withExternal()
.source(RegStatusEnum.CONNECTED)
.target(RegStatusEnum.REGISTERING)
.event(RegEventEnum.REGISTER)
.and()
.withExternal()
.source(RegStatusEnum.REGISTERING)
.target(RegStatusEnum.REGISTERING)
.event(RegEventEnum.REGISTER)
.and()
// 3.注冊成功事件
// 注冊中 -> 已注冊
.withExternal()
.source(RegStatusEnum.REGISTERING)
.target(RegStatusEnum.REGISTERED)
.event(RegEventEnum.REGISTER_SUCCESS)
.and()
.withExternal()
.source(RegStatusEnum.REGISTERED)
.target(RegStatusEnum.REGISTERED)
.event(RegEventEnum.REGISTER_SUCCESS)
.and()
// 4.注冊失敗事件
// 注冊中 -> 未連接
.withExternal()
.source(RegStatusEnum.REGISTERING)
.target(RegStatusEnum.UNCONNECTED)
.event(RegEventEnum.REGISTER_FAILED)
.and()
// 5.注銷事件
// 已連接 -> 未連接
.withExternal()
.source(RegStatusEnum.CONNECTED)
.target(RegStatusEnum.UNCONNECTED)
.event(RegEventEnum.UN_REGISTER)
.and()
// 注冊中 -> 未連接
.withExternal()
.source(RegStatusEnum.REGISTERING)
.target(RegStatusEnum.UNCONNECTED)
.event(RegEventEnum.UN_REGISTER)
.and()
// 已注冊 -> 未連接
.withExternal()
.source(RegStatusEnum.REGISTERED)
.target(RegStatusEnum.UNCONNECTED)
.event(RegEventEnum.UN_REGISTER)
;
}
Spring StateMachine 提供了注解配置實現(xiàn)方式既琴,所有 StateMachineListener 接口中定義的事件都能通過注解的方式來進(jìn)行配置實現(xiàn)。這里以連接事件為案例,@OnTransition 中 source 指定原始狀態(tài)松靡,target 指定目標(biāo)狀態(tài),當(dāng)事件觸發(fā)時將會被監(jiān)聽到從而調(diào)用 connect() 方法屠列。
@WithStateMachine
public class StateMachineEventConfig {
@OnTransition(source = "UNCONNECTED", target = "CONNECTED")
public void connect() {
System.out.println("http:///////////////////");
System.out.println("連接事件, 未連接 -> 已連接");
System.out.println("http:///////////////////");
}
@OnTransition(source = "CONNECTED", target = "REGISTERING")
public void register() {
System.out.println("http:///////////////////");
System.out.println("注冊事件, 已連接 -> 注冊中");
System.out.println("http:///////////////////");
}
@OnTransition(source = "REGISTERING", target = "REGISTERED")
public void registerSuccess() {
System.out.println("http:///////////////////");
System.out.println("注冊成功事件, 注冊中 -> 已注冊");
System.out.println("http:///////////////////");
}
@OnTransition(source = "REGISTERED", target = "UNCONNECTED")
public void unRegister() {
System.out.println("http:///////////////////");
System.out.println("注銷事件, 已注冊 -> 未連接");
System.out.println("http:///////////////////");
}
}
Spring StateMachine 讓狀態(tài)機結(jié)構(gòu)更加層次化,我們來回顧下幾個核心步驟:第一步,定義狀態(tài)枚舉浦旱。第二步例隆,定義事件枚舉皿曲。第三步,定義狀態(tài)機配置劫樟,設(shè)置初始狀態(tài)易阳,以及狀態(tài)與事件之間的關(guān)系拒课。第四步,定義狀態(tài)監(jiān)聽器扎酷,當(dāng)狀態(tài)變更時,觸發(fā)方法遏匆。
(完)
更多精彩文章法挨,盡在「服務(wù)端思維」微信公眾號!