狀態機介紹
我們在開發過程中,發現一些場景優化可以很明顯的使用狀態機模式對模型進行狀態的轉移, 狀態模式也是我們在開發的過程中常用的模式, 畢竟寫起來很簡單 ,有用一個枚舉就可以表達. 該文章給大家介紹下狀態機相關的知識點
狀態模式
狀態模式,又稱狀態對象模式(Pattern of Objects for States),狀態模式是對象的行為模式。狀態模式允許一個對象在其內部狀態改變的時候改變其行為。這個對象看上去就像是改變了它的類一樣。
狀態模式所涉及到的角色有:
- 環境(Context)角色,也成上下文:定義客戶端所感興趣的接口,並且保留一個具體狀態類的實例。這個具體狀態類的實例給出此環境對象的現有狀態。
- 抽象狀態(State)角色:定義一個接口,用以封裝環境(Context)對象的一個特定的狀態所對應的
- 具體狀態(ConcreteState)角色:每一個具體狀態類都實現了環境(Context)的一個狀態所對應的行為。
狀態模式demo
-
context
public class Context { private State state; public void doRequest(String cmd) { System.out.println(state.invoke(cmd)); } public void setState(State state) { this.state = state; } }
-
state
public interface State { String invoke(String name); } // 子類 public class QueryCmd implements State{ @Override public String invoke(String name) { return "this is a queryCMD:" + name ; } } public class UpdateCmd implements State{ @Override public String invoke(String name) { return "this is a updateCMD:" + name ; } }
-
客戶端
public static void main(String[] args) { State state = new QueryCmd(); Context context = new Context(); context.setState(state); context.doRequest("查詢"); }
環境類Context的行為request()是委派給某一個具體狀態類的。通過使用多態性原則,可以動態改變環境類Context的屬性State的內容,使其從指向一個具體狀態類變換到指向另一個具體狀態類,從而使環境類的行為dorequest()由不同的具體狀態類來執行。
狀態機
狀態機是狀態模式的一種運用,是一組狀態的集合,是協調相關信號動作,完成特定操作的控制中心。狀態機可歸納為4個要素,即當前狀態,條件,動作,下個狀態。這樣的歸納主要出於對狀態機的內在因果關系的考慮,當前狀態和條件是因,動作和下個狀態是果。對於復雜些的邏輯,用狀態機會有助於代碼比較清晰,容易維護和調試
復雜度對比
在github上TOP2 的狀態機是 spring 狀態機 和 squirrel ,這2個開源框架功能都十分強大, 但是結合自身業務要求 ,其實用不上他們強勁的功能,反而在一些簡單場景下,使用的上手難度反而增高,於是把目光投向了COLA state Machine,,在作者博客中有這種說法
在合適的地方用合適的解決方案,不能一招鮮吃遍天。就像最臭名昭著的DSL——流程引擎,就屬於那種嚴重的被濫用和過渡設計的典型,是把簡單的問題復雜化的典型。
最好不要無端增加復雜性。然而,想做簡單也不是一件容易的事,特別是在大公司,我們不僅要寫代碼,還要能沉淀“NB的技術”,最好是那種可以把老板說的一愣一愣的技術
我所需要的狀態機,也僅僅需要將狀態轉移並對數據進行持久化,使用復雜度當然是越簡單越好了
性能優化對比
COLA狀態機 還有一個概念,全程操作完全可以是無狀態的, 市面上的開源狀態機引擎,不難發現,它們之所以有狀態,主要是在狀態機里面維護了兩個狀態:初始狀態(initial state)和當前狀態(current state),如果我們能把這兩個實例變量去掉的話,就可以實現無狀態,從而實現一個狀態機只需要有一個instance就夠了。當然這樣就沒辦法獲取到狀態機instance的current state,不過在我使用的過程中也不需要知道,這樣一來,一個單例就可以維護整個狀態的流程,性能大大提高
COLA狀態機名詞概念
- State:狀態
- Event:事件,狀態由事件觸發,引起變化
- Transition:流轉,表示從一個狀態到另一個狀態
- External Transition:外部流轉,兩個不同狀態之間的流轉
- Internal Transition:內部流轉,同一個狀態之間的流轉
- Condition:條件,表示是否允許到達某個狀態
- Action:動作,到達某個狀態之后,可以做什么
- StateMachine:狀態機
流程解析
COLA實現的狀態機十分之簡單,作者說是利用空余時間寫的, 整體項目文件數量不超過30個,大部分都是interface,結構清晰易懂
- 結構圖示
COLA 狀態機demo
下面寫了個demo展示.具體內容參考的是test源碼中的內容
State枚舉
package com.example.demo.statemachine;
import lombok.Getter;
public enum TaskStatusEnum {
/**
* 任務入庫
*/
INIT(1),
/**
* 任務完成-發送積分
*/
FINISHED(2),
/**
* 任務取消
*/
CANCEL(3),
/**
* 任務完成-即將發送積分
*/
WILL_SEND(4),
;
@Getter
private Integer type;
TaskStatusEnum(Integer type) {
this.type = type;
}
}
Event枚舉
public enum TaskTypeEnums {
/**
*
*/
VIDEO(4, "視頻"),
NOVEL(5, "小說"),
;
@Getter
private Integer code;
@Getter
private String name;
TaskTypeEnums(int i, String name) {
this.code = i;
this.name = name;
}
public static TaskTypeEnums findByType(Integer taskType) {
for (TaskTypeEnums value : values()) {
if (value.code.equals(taskType)) {
return value;
}
}
return null;
}
}
context領域模型
public interface Context {
String getContent();
String getId();
}
// 實現類
public class Init2WillSendContext implements Context {
@Override
public String getContent() {
return "123";
}
@Override
public String getId(){
return "Task";
}
}
客戶端
public class StateMachineHepler {
public static void main(String[] args) {
new StateMachineHepler().doTest();
}
public void doTest() {
StateMachineBuilder<TaskStatusEnum, TaskTypeEnums, Init2WillSendContext> builder
= StateMachineBuilderFactory.create();
builder.externalTransition()
.from(TaskStatusEnum.INIT)
.to(TaskStatusEnum.WILL_SEND)
.on(TaskTypeEnums.NOVEL)
.when(checkCondition())
.perform(doAction());
builder.build("task");
StateMachineFactory.get("task")
.fireEvent(TaskStatusEnum.INIT, TaskTypeEnums.NOVEL, new Init2WillSendContext());
}
public boolean check() {
return true;
}
private Condition<Init2WillSendContext> checkCondition() {
return (ctx) -> ctx.getId().equals("Task");
}
private Action<TaskStatusEnum, TaskTypeEnums, Init2WillSendContext> doAction() {
return (from, to, event, ctx) -> {
System.out.println(ctx.getContent() + " is operating " + ctx.getId() + " from:" + from + " to:" + to + " on:" + event);
};
}
}
以上代碼執行后能清晰看到狀態從INIT 狀態轉移到了WILL_SEND 狀態,其中判斷條件是啥.實用的場景
代碼解析
首先是com.alibaba.cola.statemachine.builder.StateMachineBuilderImpl
// 一共是提供2個功能。
// 1. 選擇用哪個Transaction去初始化
// 2. 將初始化的值state Machine 注冊到factory中,后面可以直接通過id靜態獲取對應注冊的狀態機
public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {
/**
* StateMap is the same with stateMachine, as the core of state machine is holding reference to states.
*/
private final Map<S, State< S, E, C>> stateMap = new ConcurrentHashMap<>();
private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);
/**
外部擴展的狀態流轉
*/
@Override
public ExternalTransitionBuilder<S, E, C> externalTransition() {
return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
}
/**
可滿足多個狀態時都去執行
*/
@Override
public ExternalTransitionsBuilder<S, E, C> externalTransitions() {
return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
}
/**
內部擴展的狀態流轉
*/
@Override
public InternalTransitionBuilder<S, E, C> internalTransition() {
return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL);
}
/**
*加到狀態機factory中
*/
@Override
public StateMachine<S, E, C> build(String machineId) {
stateMachine.setMachineId(machineId);
stateMachine.setReady(true);
StateMachineFactory.register(stateMachine);
return stateMachine;
}
}
構建出狀態流轉實體
// 狀態流轉的構建類
class TransitionBuilderImpl<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C>, From<S,E,C>, On<S,E,C>, To<S,E,C> {
// 將所有存在的上層傳入的State 轉成代碼中適用的State類
final Map<S, State<S, E, C>> stateMap;
// 當前的state類
private State<S, E, C> source;
// 目前要轉向的State 類
protected State<S, E, C> target;
// 狀態流轉類
private Transition<S, E, C> transition;
// 流程 類型 是 內部流轉還是外部流程
final TransitionType transitionType;
// 構造函數, 傳入的空map 和當前的流程類型
public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
this.stateMap = stateMap;
this.transitionType = transitionType;
}
// from方法
@Override
public From<S, E, C> from(S stateId) {
// 從map里面獲取, 看哈 這個state有沒有被初始化過,沒有的話就初始化, 並在這個map里面緩存上;
source = StateHelper.getState(stateMap, stateId);
return this;
}
// to 方法
@Override
public To<S, E, C> to(S stateId) {
target = StateHelper.getState(stateMap, stateId);
return this;
}
// within 方法 就是同一個狀態下的流轉
@Override
public To<S, E, C> within(S stateId) {
source = target = StateHelper.getState(stateMap, stateId);
return this;
}
@Override
public When<S, E, C> when(Condition<C> condition) {
transition.setCondition(condition);
return this;
}
/**條件判斷
*/
@Override
public On<S, E, C> on(E event) {
transition = source.addTransition(event, target, transitionType);
return this;
}
/** 執行對應方法
*/
@Override
public void perform(Action<S, E, C> action) {
transition.setAction(action);
}
}
后續,開始補充from
,to
,on
,perform
等方法,最后調用build
方法放入本地緩存,然后使用的時候去調用StateMachineFactory#get.fireEvent
public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {
// 狀態機id
private String machineId;
// 狀態機當前已經初始化的statemap
private final Map<S, State<S, E, C>> stateMap;
// 判斷當前狀態機是不是有build 過
private boolean ready;
// 在build的時候初始化,並且傳入已經設置好的幾個狀態map
public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {
this.stateMap = stateMap;
}
// 執行event
@Override
public S fireEvent(S sourceStateId, E event, C ctx) {
//。首選判斷是不是已經有過build
isReady();
//。去獲取對應的eventid 和狀態流轉
Transition<S, E, C> transition = routeTransition(sourceStateId, event, ctx);
if (transition == null) {
Debugger.debug("There is no Transition for " + event);
return sourceStateId;
}
// 返回執行之后的狀態
return transition.transit(ctx).getId();
}
private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
State sourceState = getState(sourceStateId);
List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);
if (transitions == null || transitions.size() == 0) {
return null;
}
Transition<S, E, C> transit = null;
for (Transition<S, E, C> transition : transitions) {
if (transition.getCondition() == null) {
transit = transition;
} else if (transition.getCondition().isSatisfied(ctx)) {
transit = transition;
break;
}
}
return transit;
}
private State getState(S currentStateId) {
State state = StateHelper.getState(stateMap, currentStateId);
if (state == null) {
showStateMachine();
throw new StateMachineException(currentStateId + " is not found, please check state machine");
}
return state;
}
private void isReady() {
if (!ready) {
throw new StateMachineException("State machine is not built yet, can not work");
}
}
@Override
public String accept(Visitor visitor) {
StringBuilder sb = new StringBuilder();
sb.append(visitor.visitOnEntry(this));
for (State state : stateMap.values()) {
sb.append(state.accept(visitor));
}
sb.append(visitor.visitOnExit(this));
return sb.toString();
}
@Override
public void showStateMachine() {
SysOutVisitor sysOutVisitor = new SysOutVisitor();
accept(sysOutVisitor);
}
@Override
public String generatePlantUML() {
PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor();
return accept(plantUMLVisitor);
}
@Override
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public void setReady(boolean ready) {
this.ready = ready;
}
}
注意一點的是在4.0.0
中有個坑
com.alibaba.cola.statemachine.impl.StateImpl
public class StateImpl<S,E,C> implements State<S,E,C> {
protected final S stateId;
// 這里是使用的map ,key對應event,value 對應transition ,則如果from的state是相同的,在addTransition去put的時候新的會覆蓋老的transition
private HashMap<E, Transition<S, E,C>> transitions = new HashMap<>();
StateImpl(S stateId){
this.stateId = stateId;
}
@Override
public Transition<S, E, C> addTransition(E event, State<S,E,C> target, TransitionType transitionType) {
Transition<S, E, C> newTransition = new TransitionImpl<>();
newTransition.setSource(this);
newTransition.setTarget(target);
newTransition.setEvent(event);
newTransition.setType(transitionType);
Debugger.debug("Begin to add new transition: "+ newTransition);
verify(event, newTransition);
transitions.put(event, newTransition);
return newTransition;
}
...
}
而在4.1.0 沒有使用這種寫法,而是使用的list避免了覆蓋問題
這樣整個流程就執行完了,整體結構比較小巧,也為我們在改編重構上提供了便利, 如結合srping容器,如添加注解形式的state Machine等.
參考文章
https://blog.csdn.net/significantfrank/article/details/104996419