對於後台業務實現方式的重構,源於目前的業務場景太多太複雜,我們寫**的時候總不免落入「偽物件導向」的方式,以瀑布流的形式一直在原來的業務**上疊新的**,建立不同的分支。導致每提乙個小需求,就要在各個地方去增加**,而且還會遺漏,並且**的質量難以維護。
出於這樣的困境,我們想將現在實現的業務場景進行高度抽象和歸類,很多業務**,無非是對資料狀態的變更。比如一條資料a,在業務場景a下,只是從狀態a翻轉成狀態b。而在場景b下,除了翻轉狀態,還會去發一條簡訊。
場景a:資料a(狀態a -> 狀態b)
場景b:資料a(1. 狀態a -> 狀態b 2. 發簡訊)
所以這裡完全用狀態機的機制去實現狀態翻轉的工作,另外再處理額外的功能(比如場景b的發簡訊)。
狀態機的理解是:資料a遇到事件1,會從源狀態翻轉到目標狀態
資料a: 源狀態 --(事件1)--> 目標狀態
1. 狀態機表
我們用了一張狀態機表來儲存這種變化關係:
create table `task_status_trans_rule` (
`id` bigint(64) not null auto_increment comment 'id',
`task_type` varchar(25) default null comment '任務型別',
`src_status` varchar(15) default null comment '源狀態',
`dest_status` varchar(15) default null comment '目標狀態',
`event_type_name` varchar(32) default null comment '事件型別',
`remark` varchar(50) default null comment '描述',
`create_time` datetime default null comment '建立時間',
`modify_time` datetime default null comment '修改時間',
primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8mb4 comment='任務狀態轉換規則表';
id
task_type
src_status
dest_status
event_type_name
remark
create_time
modify_time
1task_1
status_a
status_b
event_a
任務1-狀態a-狀態b-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
2task_2
status_c
status_d
event_b
任務2-狀態c-狀態d-事件b
2019-05-10 09:00:00
2019-05-10 09:00:00
其中,一條資料就會有一種對應的任務型別,某種事件型別會將其狀態進行翻轉。在這裡我們可以看到,普通的狀態翻轉都是在**上寫死的,不能靈活修改(修改狀態需要在下一版本發版解決),而且需要找到**中所有需要修改的地方進行修改。
update table set status = 'close' where id = 1;
而用狀態機表來維護資料狀態翻轉狀態的好處,是不需要修改**(開放閉合),且統一管理。我們專案將所有業務場景的狀態翻轉維護在這張表裡,也僅僅有200多行資料。
2. 狀態機的繼承
狀態機表裡的狀態翻轉資料雖說只有幾百條,但這些跟業務強相關的基礎資料盡量要保持不變,因為一旦修改,很難馬上從肉眼上判斷出這次修改對業務邏輯的具體影響,需要很好的回歸測試。另外,對於狀態機表資料本身,其實也隱含了「子類繼承父類」的關係。
比如:id
task_type
src_status
dest_status
event_type_name
remark
create_time
modify_time
1task
status_a
status_b
event_a
任務-狀態a-狀態b-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
2task_1
status_a
status_b
event_a
任務1-狀態a-狀態b-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
3task_2
status_a
status_b
event_a
任務2-狀態a-狀態b-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
4task_3
status_a
status_c
event_a
任務3-狀態a-狀態c-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
從表中可看出,對於同乙個事件event_a,且資料的源狀態都是status_a的前提下,task、task_1和task_2都是翻轉成status_b,而task_3是翻轉成status_c。
task是父類,task_1、task_2、task3都是task的子類,這種父子類的關係,是通過「相同字首」(都有相同的字首:task)來實現的。由於task_1和task_2的狀態機和其父類task的狀態機一致,所以可以用繼承父類的方式來減少配置的狀態機資料。而task_3的狀態機和task的狀態機不一致,就是保持子類的特殊性。
故上表可改造成:
idtask_type
src_status
dest_status
event_type_name
remark
create_time
modify_time
1task
status_a
status_b
event_a
任務-狀態a-狀態b-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
2task_3
status_a
status_c
event_a
任務3-狀態a-狀態c-事件a
2019-05-10 09:00:00
2019-05-10 09:00:00
表中已經不再重複定義task_1和task_2的狀態機,那在**上如何獲取到task_1的狀態機呢?
對狀態表進行查詢,查詢條件是task_type, src_status, event_type_name,查詢結果是dest_status
首先用task_1進行查詢:
select dest_status from task_status_trans_rule
where task_type = 'task_1' and src_status = 'status_a' and event_type_name = 'event_a';
如果此查詢有結果,說明有子類特別的狀態機。(此時task_1肯定沒結果,因為表中沒有task_1的狀態機。而查詢task_3是有結果的,為status_c)
如果沒有結果,則將task_1改為其父類task,再次查詢:
select dest_status from task_status_trans_rule
where task_type = 'task' and src_status = 'status_a' and event_type_name = 'event_a';
其結果是status_b,則實現了狀態機的繼承,這可大大減少狀態機資料的配置量。
3. 狀態機的實現
由前面可知,很多業務場景都只涉及到狀態翻轉,只有一些複雜場景會有額外的操作,比如發簡訊。
故我們定義乙個狀態機抽象類abstracttaskeventlistener,先從狀態機表獲取到對應事件的資料,執行統一的翻轉狀態方法trueto(),額外的操作在具體的子類中通過重寫父類的抽象方法processevent()來實現。
public abstract class abstracttaskeventlistener
}protected void processevent(task task, event event)
}
當業務場景還涉及額外的業務邏輯,就可定義乙個子類,在processevent寫具體的業務邏輯。
@component
public class subtaskeventlistener extends abstracetaskeventlistener
}
基於狀態機的按鍵程式
基於狀態機的按鍵程式 一般的按鍵輸入軟體介面程式非常簡單,在程式中一旦檢測到按鍵輸入口為低電平 有時可能為高 便採用軟體延時的方法來進行消抖,然後再次檢測按鍵輸入,如果再次確認為低電平則表示有按鍵按下,轉入執行按鍵處理程式。如果延時後檢測的電平為高電平則放棄本次按鍵檢測,重新開始一次按鍵檢測過程。在...
基於狀態機的遊戲框架
有限狀態機就是乙個具有有限數量狀態,並且能夠根據相應的操作從乙個狀態變換到另乙個狀態,而在同一時刻只能處在一種狀態下的智慧型體。英文 finite state machine 簡稱 fsm 最簡單的狀態機 if else 實際上if else就是乙個最有兩種狀態的狀態機,分別是true和false ...
基於狀態機的簡單控制
基於狀態機的簡單控制 公司是做一些簡單的夾具的,大部分功能是幾個按鍵加上幾個電磁閥,再加乙個顯示屏就構成了乙個簡單的控制系統。工控行業一般要求是比較穩定的,所以我需要在啟動電磁閥之後去判斷我的氣缸是否到了指定位置,這樣我們就需要用到了sensor,那麼問題來了 首先我按下了按鍵,氣缸也動作了,接下來...