EventBus-實現java狀態機
轉自https://www.jianshu.com/p/8def04b34b3c
首先,瞭解狀態機是什麼,我們為什麼需要狀態機!
舉個最簡單例子,請假,作為一個最底層程式設計師,每次請假都要領導層層審批,而假有分為很多種,事假,病假,婚假,年休假等等,當然選擇請的假不同,審批標準也不同,不同的假單需要走的審批鏈也不一樣,比如年休假,可能只需要領導審批扣掉年休假即可,請病假需要領導審批,領導審批之後,先休假,等休完假回來提交病假的材料,由hr審批之後才能完成整個請假過程。更有甚者,如果你要修一個一個月的長假,就不僅僅是需要直線領導hr審批,可能還需要公司ceo審批 ,審批通過後,才能通過。如下圖:
當然,實際來講,請假的種類和鏈路比這個要複雜的多,我們一般會怎麼實現,是否要使用if else了,對應不同的假單,走不同的分支,程式碼寫出來就成了一個非常複雜的,多級巢狀的程式碼了,後面如何維護程式碼,多了幾種假的種類,是不是又要if else了。如下程式碼:
public void requestLeavePermit(String type){
if(type.equals("事假")){
//領導審批->hr審批->ceo審批->完成
}else if(type.equals("病假")){
//領導審批->休假->補充病例->hr審批->完成
}else if(type.equals("年休假")){
//領導審批->hr審批->通過
}else if(type.equals("產假")){
//領導審批->hr審批->通過
}else if(type.equals("調休假")){
//領導審批->ceo審批->通過
}
}
或者寫成這個樣子:
public void requestLeavePermit(String type,String userName){
switch (type){
case "事假":
//領導審批->hr審批->ceo審批->完成
break;
case "病假":
//領導審批->休假->補充病例->hr審批->完成
break;
case "年休假":
//領導審批->hr審批->通過
break;
case "產假":
//領導審批->hr審批->通過
break;
case "調休假":
//領導審批->ceo審批->通過
default:
break;
}
}
if,else巢狀太深,然後每個if,else又是自己的處理流程,這樣程式碼結構會原來越複雜,當審批鏈發生變更,這個時候會發現程式碼耦合性太強,導致修改起來很麻煩。
如何解決這個問題,我們不難看到,所有的請假都經過了這樣幾個階段,從請假開始,提交假單,然後領導審批,hr審批,ceo審批,只是不同的是,有些審批流程多了稽核人或者是少了稽核人,每種假單稽核材料有所不同而已。
我們如何使用狀態機來如何解決程式碼耦合性的問題,提高程式碼可擴充套件性可讀性。
如果我們把領導審批,hr審批,ceo審批,分別看做一個動作,每個相應都有幾個狀態,審批通過,不通過,拒絕,重新稽核,會怎麼樣?
首先,我們將請假的型別定義成一個列舉:
public enum LeavePermitEnum {
ANNUAL_LEAVE("annual_leave","年休假 "),
CASUAL_LEAVE("casual_leave","事假"),
MEDICAL_LEAVE("medical_leave","病假"),
MARRIAGE_LEAVE("marriage_leave","婚假"),;
private String type;
private String memo;
//此處忽略構造方法和set/get方法
}
領導審批,hr審批,ceo審批,都有一個審批意見(通過,拒絕,或者是重修修改假單補充材料等),在這裡,相當於一個事件Event,於是,整個狀態扭轉也可以用一個列舉類來表示,審批意見由一個列舉類Event來表示。
public enum Event {
AGREE("agree","同意"),
DISSAGREE("disagree","不同意"),
MODIFY("modify","修改"),
;
private String type;
private String memo;
}
因此,一個假單的狀態就有很多種,用一個列舉代表整個假單的狀態:
public enum Status {
//提交假單
PERMIT_SUBMIT("permitSubmit","提交假單"),
//領導審批
LEADER_PERMITING("leaderPermiting","領導審批中"),
LEADER_PERMIT_AGREE("leaderAgree","領導同意"),
LEADER_PERMIT_DISAGREE("leaderDisAgree","領導不同意"),
LEADER_PERMIT_MODIFY("leaderModify","領導覺得需要補充材料重修修改"),
//hr審批
HR_PERMITING("hrPermiting","hr審批中"),
HR_PERMIT_AGREE("hrAgree","hr同意"),
HR_PERMIT_DISAGREE("hrDisAgree","hr不同意"),
HR_PERMIT_MODIFY("hrModify","hr覺得需要補充材料重修修改"),
//ceo審批
CEO_PERMITING("ceoPermiting","領導審批中"),
CEO_PERMIT_AGREE("ceoAgree","ceo同意"),
CEO_PERMIT_DISAGREE("ceoDisAgree","ceo不同意"),
CEO_PERMIT_MODIFY("ceoModify","ceo覺得需要補充材料重修修改"),
//最終請假狀態
PERMIT_SUCCESS("permitSuccess","請假成功"),
PERMIT_FAIL("permitFail","請假失敗")
;
private String status;
private String memo;
private Status(String status,String memo){
this.status=status;
this.memo=memo;
}
}
狀態定義清楚之後,需要考慮兩個問題
- 從當前狀態需要能夠跳轉到下一個狀態,比如提交假單之後,要能夠從提交假單狀態跳轉到領導審批狀態。
- 不同的審批意見要能夠跳轉不同的狀態,比如領導審批狀態跳轉審批通過,或者拒絕該審批需要能夠按照Event狀態跳轉不同的狀態。
這塊功能可以交給狀態機StatusMachine去解決,由當前狀態+事件驅動(也就是當前請假的狀態和審批意見)獲取下一個狀態。
我們知道,請假的種類不同,所走的流程也不同,相應的處理也不同,每種假單都有自己的審批鏈,也對應每種假單有不同的狀態機,不難設計StatusMachine為介面或抽象類。狀態機只做一件事情,根據event(審批意見),跳轉下一個狀態機。
public interface StatusMachine {
/**
*@params status 當前狀態
*@params event 審批意見
*@return 下一個狀態
**/
public Status getNextStatus(Status status,Event event);
}
這裡舉兩個例子,一個病假,一個年休假的實現:
年休假的審批流程:
- 提交假單 PERMIT_SUBMIT
- 領導審批 LEADER_PERMITING
- 等待領導審批
- 領導審批通過/不通過/拒絕
- 領導審批通過 LEADER_PERMIT_AGREE
- ceo審批 CEO_PERMITING
- 等待ceo審批意見
- ceo審批通過/不通過/拒絕
- ceo審批通過 CEO_PERMIT_AGREE
- 請假完成 PERMIT_SUCCESS
因此事假的狀態機StatusMachine實現如下:
public class AnnualLeaveStatusMachine implements StatusMachine{
public Status getNextStatus(Status status,Event event){
switch (status){
case PERMIT_SUBMIT:
//提交假單狀態無需審批跳轉領導審批中狀態
return Status.LEADER_PERMITING;
case LEADER_PERMITING:
//領導審批需要審批意見 審批意見不用返回不同的狀態
return getLeaderPermitStatus(event);
case LEADER_PERMIT_AGREE:
//領導同意請假,則跳轉ceo審批
return Status.CEO_PERMITING;
case LEADER_PERMIT_DISAGREE:
//領導不同意該假單,則請假失敗
return Status.PERMIT_FAIL;
case LEADER_PERMIT_MODIFY:
return getLeaderPermitStatus(event);
case CEO_PERMITING:
//ceo審批需要審批意見
return getCEOPermitStatus(event);
case CEO_PERMIT_AGREE:
// ceo審批同意 跳轉審批通過 請假完成
return Status.PERMIT_SUCCESS;
case CEO_PERMIT_DISAGREE:
//ceo不同意審批 則跳轉審批失敗
return Status.PERMIT_FAIL;
case CEO_PERMIT_MODIFY:
return getCEOPermitStatus(event);
default:
throw new RuntimeException("沒有該流程");
}
}
private Status getLeaderPermitStatus(Event event){
switch (event){
case AGREE:
//領導審批通過 返回同意該假單
return Status.LEADER_PERMIT_AGREE;
case DISSAGREE:
//領導不同意 則返回領導拒絕改假單狀態
return Status.LEADER_PERMIT_DISAGREE;
case MODIFY:
return Status.LEADER_PERMIT_MODIFY;
default:
throw new RuntimeException("不支援該Event審批意見");
}
}
private Status getCEOPermitStatus(Event event){
switch (event){
case AGREE:
//ceo審批通過 則返回ceo同意該假單
return Status.CEO_PERMIT_AGREE;
case DISSAGREE:
// ceo審批不通過 則返回ceo不同意該假單狀態
return Status.CEO_PERMIT_DISAGREE;
case MODIFY:
return Status.CEO_PERMIT_MODIFY;
default:
throw new RuntimeException("不支援該Event審批意見");
}
}
}
病假的審批流程:
- 提交假單 PERMIT_SUBMIT
- 領導審批 LEADER_PERMITING
- 等待領導審批
- 領導審批通過/不通過/拒絕
- 領導審批通過 LEADER_PERMIT_AGREE
- HR審批 HR_PERMITING
- 等待HR審批意見
- HR審批通過/不通過/拒絕
- HR審批通過 CEO_PERMIT_AGREE
- 請假完成 PERMIT_SUCCESS
根據該流程不難設計出該狀態機
public class MedicalLeaveStatusMachine implements StatusMachine{
public Status getNextStatus(Status status,Event event){
switch (status){
case PERMIT_SUBMIT:
//提交假單狀態直接跳轉領導審批中狀態
return Status.LEADER_PERMITING;
case LEADER_PERMITING:
//領導審批中狀態需要審批意見再獲取下一個狀態
return getLeaderPermitStatus(event);
case LEADER_PERMIT_AGREE:
//領導同意審批該假單 跳轉hr審批中狀態
return Status.HR_PERMITING;
case LEADER_PERMIT_DISAGREE:
//領導不同意則返回請假失敗
return Status.PERMIT_FAIL;
case LEADER_PERMIT_MODIFY:
return getLeaderPermitStatus(event);
case HR_PERMITING:
//hr審批根據審批意見跳轉下一個狀態
return getHrPermitStatus(event);
case HR_PERMIT_AGREE:
//hr審批通過跳轉審批完成狀態
return Status.PERMIT_SUCCESS;
case HR_PERMIT_DISAGREE:
// hr審批不同意 返回請假失敗
return Status.PERMIT_FAIL;
case HR_PERMIT_MODIFY:
return getHrPermitStatus(event);
default:
throw new RuntimeException("沒有該流程");
}
}
private Status getLeaderPermitStatus(Event event){
switch (event){
case AGREE:
//領導同意該假單,則返回領導審批通過
return Status.LEADER_PERMIT_AGREE;
case DISSAGREE:
//領導不同意該假單 則返回領導審批不通過
return Status.LEADER_PERMIT_DISAGREE;
case MODIFY:
return Status.LEADER_PERMIT_MODIFY;
default:
throw new RuntimeException("不支援該Event審批意見");
}
}
private Status getHrPermitStatus(Event event){
switch (event){
case AGREE:
//hr審批同意該假單,則返回hr同意狀態
return Status.HR_PERMIT_AGREE;
case DISSAGREE:
//hr審批不同意該假單,則返回hr不同意狀態
return Status.HR_PERMIT_DISAGREE;
case MODIFY:
return Status.HR_PERMIT_MODIFY;
default:
throw new RuntimeException("不支援該Event審批意見");
}
}
}
對於請假的員工來講,只知道提交了一個假單,並不會關心到底該流程怎麼走,所以在設計的時候,需要根據請假型別能夠自動匹配狀態機,這裡可以用靜態工廠去實現。
public class StatusMachineFactory {
private StatusMachineFactory(){
}
/**
* 根據狀態獲取狀態機
* @param leavePermitType
* @return 對應請假型別的狀態機
*/
public static StatusMachine getStatusMachine(LeavePermitType leavePermitType){
switch (leavePermitType){
case MEDICAL_LEAVE:
return new MedicalLeaveStatusMachine();
case ANNUAL_LEAVE:
return new AnnualLeaveStatusMachine();
default:
throw new RuntimeException("未知型別");
}
}
}
狀態機設計好之後,每個狀態都應該對應有該狀態的處理類,且需要統一管理該狀態和處理類的關係。
以年休假為例:提交假單->領導審批4個狀態->ceo審批4個狀態->請假完成/失敗2個狀態。
總計需要11個狀態處理物件去處理該狀態。
該狀態處理類需要具備哪些能力:
- 處理該狀態的業務
- 能夠決定要不要扭轉該狀態機接著往下走(提交假單狀態處理結束要能夠自動執行到領導審批狀態,領導審批狀態不能接著扭轉到下一個狀態,需要等待領導的審批意見才可繼續往下走)
不難設計,先抽象出一個StatusHandler介面或父類,每個狀態的處理類去實現該介面或繼承該父類,在statusHandler中,有三個方法,before,dohandler,after,after主要負責扭轉狀態機,獲取下一個狀態的處理類處理下一個狀態的事件。如果狀態到達某一個狀態不需要往下繼續執行,則重寫after方法即可中斷狀態機,dohandler主要負責做業務處理。
public interface AbstractStatusHandler {
public void handle(LeavePermit leavePermit);
}
public abstract class StatusHandler implements AbstractStatusHandler{
protected void before(LeavePermit leavePermit){
}
public void handle(LeavePermit leavePermit){
before(leavePermit);
doHandler(leavePermit);
after(leavePermit);
}
protected abstract void doHandler(LeavePermit leavePermit);
protected void after(LeavePermit leavePermit){
//去下一個狀態的處理物件處理
goNextStatusHandler(leavePermit);
}
protected void goNextStatusHandler(LeavePermit leavePermit){
//獲取下一個狀態
leavePermit.setStatus(StatusMachineFactory.getStatusMachine(leavePermit.getLeavePermitType()).getNextStatus(leavePermit.getStatus(),leavePermit.getEvent()));
//狀態機引擎驅動假單處理
StatusMachineEngine.post(leavePermit);
}
在看一下具體的狀態處理類實現,11個狀態對應11個處理類,這裡列舉出部分
public class AnnualPermitSubmitStatusHandler extends StatusHandler{
protected void doHandler(LeavePermit leavePermit){
System.out.println(String.format("user:%s--提交年休假假單--leavePermit status:%s",leavePermit.getUser(),leavePermit.getStatus().getStatus()));
}
}
public class AnnualLeaderPermitingStatusHandler extends StatusHandler{
protected void doHandler(LeavePermit leavePermit){
System.out.println(String.format("user:%s--領導審批年休假中--leavePermit status:%s",leavePermit.getUser(),leavePermit.getStatus().getStatus()));
}
@Override
protected void after(LeavePermit leavePermit){
if(leavePermit.getEvent()==null){
//還未審批,狀態機結束,等待審批意見
System.out.println(String.format("user:%s--等待領導審批--leavePermit status:%s",leavePermit.getUser(),leavePermit.getStatus().getStatus()));
return;
}
super.goNextStatusHandler(leavePermit);
}
}
public class AnnualLeaderAgreeStatusHandler extends StatusHandler{
protected void doHandler(LeavePermit leavePermit){
System.out.println(String.format("user:%s--直線領導同意請年休假--leavePermit status:%s",leavePermit.getUser(),leavePermit.getStatus().getStatus()));
}
}
public class AnnualLeaderAgreeStatusHandler extends StatusHandler{
protected void doHandler(LeavePermit leavePermit){
leavePermit.setEvent(null);
System.out.println(String.format("user:%s--直線領導同意請年休假--leavePermit status:%s",leavePermit.getUser(),leavePermit.getStatus().getStatus()));
}
}
public class AnnualCEOPermitingStatusHandler extends StatusHandler{
protected void doHandler(LeavePermit leavePermit){
System.out