重學 Java 設計模式:實戰狀態模式「模擬系統營銷活動,狀態流程稽核釋出上線場景」

小傅哥發表於2020-07-03


作者:小傅哥
部落格:https://bugstack.cn - 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!?

@

一、前言

寫好程式碼三個關鍵點

如果把寫程式碼想象成家裡的軟裝,你肯定會想到家裡需要有一個非常不錯格局最好是南北通透的,買回來的傢俱最好是品牌保證質量的,之後呢是大小合適,不能擺放完了看著彆扭。那麼把這一過程抽象成寫程式碼就是需要三個核心的關鍵點;架構(房間的格局)、命名(品牌和質量)、註釋(尺寸大小說明書),只有這三個點都做好才能完成出一套賞心悅目的

平原走碼?易放難收

上學期間你寫了多少程式碼?上班一年你能寫多少程式碼?回家自己學習寫了多少程式碼?個人素養的技術棧地基都是一塊一塊磚碼出來的,寫的越廣越深,根基就越牢固。當根基牢固了以後在再上層建設就變得迎刃而解了,也更容易建設了。往往最難的就是一層一層階段的突破,突破就像破殼一樣,也像夯實地基,短時間看不到成績,也看不出高度。但以後誰能走的穩,就靠著默默的沉澱。

技術傳承的重要性

可能是現在時間節奏太快,一個需求下來恨不得當天就上線(這個需求很簡單,怎麼實現我不管,明天上線!),導致團隊的人都很慌很急很累很崩潰,最終反反覆覆的人員更替,專案在這個過程中也交接了N次,文件不全、程式碼混亂、錯綜複雜,誰在後面接手也都只能修修補補,就像爛尾樓。這個沒有傳承、沒有沉澱的專案,很難跟隨業務的發展。最終!根基不牢,一地雞毛。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回覆原始碼下載獲取(開啟獲取的連結,找到序號18)
工程 描述
itstack-demo-design-19-00 場景模擬工程;模擬營銷活動操作服務(查詢、稽核)
itstack-demo-design-19-01 使用一坨程式碼實現業務需求
itstack-demo-design-19-02 通過設計模式優化改造程式碼,產生對比性從而學習

三、狀態模式介紹

狀態模式,圖片來自 refactoringguru.cn

狀態模式描述的是一個行為下的多種狀態變更,比如我們最常見的一個網站的頁面,在你登入與不登入下展示的內容是略有差異的(不登入不能展示個人資訊),而這種登入不登入就是我們通過改變狀態,而讓整個行為發生了變化。

收音機&放音機&磁帶機

至少80後、90後的小夥伴基本都用過這種磁帶放音機(可能沒有這個好看),它的上面是一排按鈕,當放入磁帶後,通過上面的按鈕就可以讓放音機播放磁帶上的內容(listen to 英語聽力考試),而且有些按鈕是互斥的,當在某個狀態下才可以按另外的按鈕(這在設計模式裡也是一個關鍵的點)。

四、案例場景模擬

場景模擬;營銷活動稽核狀態流轉

在本案例中我們模擬營銷活動稽核狀態流轉場景(一個活動的上線是多個層級稽核上線的)

在上圖中也可以看到我們的流程節點中包括了各個狀態到下一個狀態扭轉的關聯條件,比如;稽核通過才能到活動中,而不能從編輯中直接到活動中,而這些狀態的轉變就是我們要完成的場景處理。

大部分程式設計師基本都開發過類似的業務場景,需要對活動或者一些配置需要稽核後才能對外發布,而這個稽核的過程往往會隨著系統的重要程度而設立多級控制,來保證一個活動可以安全上線,避免造成資損。

當然有時候會用到一些審批流的過程配置,也是非常方便開發類似的流程的,也可以在配置中設定某個節點的審批人員。但這不是我們主要體現的點,在本案例中我們主要是模擬學習對一個活動的多個狀態節點的稽核控制。

1. 場景模擬工程

itstack-demo-design-19-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── ActivityInfo.java
                ├── Status.java
                └── ActivityService.java
  • 在這個模擬工程裡我們提供了三個類,包括;狀態列舉(Status)、活動物件(ActivityInfo)、活動服務(ActivityService),三個服務類。
  • 接下來我們就分別介紹三個類包括的內容。

2. 程式碼實現

2.1 基本活動資訊

public class ActivityInfo {

    private String activityId;    // 活動ID
    private String activityName;  // 活動名稱
    private Enum<Status> status;  // 活動狀態
    private Date beginTime;       // 開始時間
    private Date endTime;         // 結束時間
   
    // ...get/set
}  
  • 一些基本的活動資訊;活動ID、活動名稱、活動狀態、開始時間、結束時間。

2.2 活動列舉狀態

public enum Status {

    // 1建立編輯、2待稽核、3稽核通過(任務掃描成活動中)、4稽核拒絕(可以撤審到編輯狀態)、5活動中、6活動關閉、7活動開啟(任務掃描成活動中)
    Editing, Check, Pass, Refuse, Doing, Close, Open

}
  • 活動的列舉;1建立編輯、2待稽核、3稽核通過(任務掃描成活動中)、4稽核拒絕(可以撤審到編輯狀態)、5活動中、6活動關閉、7活動開啟(任務掃描成活動中)

2.3 活動服務介面

public class ActivityService {

    private static Map<String, Enum<Status>> statusMap = new ConcurrentHashMap<String, Enum<Status>>();

    public static void init(String activityId, Enum<Status> status) {
        // 模擬查詢活動資訊
        ActivityInfo activityInfo = new ActivityInfo();
        activityInfo.setActivityId(activityId);
        activityInfo.setActivityName("早起學習打卡領獎活動");
        activityInfo.setStatus(status);
        activityInfo.setBeginTime(new Date());
        activityInfo.setEndTime(new Date());
        statusMap.put(activityId, status);
    }

    /**
     * 查詢活動資訊
     *
     * @param activityId 活動ID
     * @return 查詢結果
     */
    public static ActivityInfo queryActivityInfo(String activityId) {
        // 模擬查詢活動資訊
        ActivityInfo activityInfo = new ActivityInfo();
        activityInfo.setActivityId(activityId);
        activityInfo.setActivityName("早起學習打卡領獎活動");
        activityInfo.setStatus(statusMap.get(activityId));
        activityInfo.setBeginTime(new Date());
        activityInfo.setEndTime(new Date());
        return activityInfo;
    }

    /**
     * 查詢活動狀態
     *
     * @param activityId 活動ID
     * @return 查詢結果
     */
    public static Enum<Status> queryActivityStatus(String activityId) {
        return statusMap.get(activityId);
    }

    /**
     * 執行狀態變更
     *
     * @param activityId   活動ID
     * @param beforeStatus 變更前狀態
     * @param afterStatus  變更後狀態 b
     */
    public static synchronized void execStatus(String activityId, Enum<Status> beforeStatus, Enum<Status> afterStatus) {
        if (!beforeStatus.equals(statusMap.get(activityId))) return;
        statusMap.put(activityId, afterStatus);
    }

}
  • 在這個靜態類中提供了活動的查詢和狀態變更介面;queryActivityInfoqueryActivityStatusexecStatus
  • 同時使用Map的結構來記錄活動ID和狀態變化資訊,另外還有init方法來初始化活動資料。實際的開發中這類資訊基本都是從資料庫或者Redis中獲取。

五、用一坨坨程式碼實現

這裡我們先使用最粗暴的方式來實現功能

對於這樣各種狀態的變更,最讓我們直接想到的就是使用ifelse進行判斷處理。每一個狀態可以流轉到下一個什麼狀態,都可以使用巢狀的if實現。

1. 工程結構

itstack-demo-design-19-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── ActivityExecStatusController.java
                └── Result.java
  • 整個實現的工程結構比較簡單,只包括了兩個類;ActivityExecStatusControllerResult,一個是處理流程狀態,另外一個是返回的物件。

2. 程式碼實現

public class ActivityExecStatusController {

    /**
     * 活動狀態變更
     * 1. 編輯中 -> 提審、關閉
     * 2. 稽核通過 -> 拒絕、關閉、活動中
     * 3. 稽核拒絕 -> 撤審、關閉
     * 4. 活動中 -> 關閉
     * 5. 活動關閉 -> 開啟
     * 6. 活動開啟 -> 關閉
     *
     * @param activityId   活動ID
     * @param beforeStatus 變更前狀態
     * @param afterStatus  變更後狀態
     * @return 返回結果
     */
    public Result execStatus(String activityId, Enum<Status> beforeStatus, Enum<Status> afterStatus) {

        // 1. 編輯中 -> 提審、關閉
        if (Status.Editing.equals(beforeStatus)) {
            if (Status.Check.equals(afterStatus) || Status.Close.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        // 2. 稽核通過 -> 拒絕、關閉、活動中
        if (Status.Pass.equals(beforeStatus)) {
            if (Status.Refuse.equals(afterStatus) || Status.Doing.equals(afterStatus) || Status.Close.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        // 3. 稽核拒絕 -> 撤審、關閉
        if (Status.Refuse.equals(beforeStatus)) {
            if (Status.Editing.equals(afterStatus) || Status.Close.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        // 4. 活動中 -> 關閉
        if (Status.Doing.equals(beforeStatus)) {
            if (Status.Close.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        // 5. 活動關閉 -> 開啟
        if (Status.Close.equals(beforeStatus)) {
            if (Status.Open.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        // 6. 活動開啟 -> 關閉
        if (Status.Open.equals(beforeStatus)) {
            if (Status.Close.equals(afterStatus)) {
                ActivityService.execStatus(activityId, beforeStatus, afterStatus);
                return new Result("0000", "變更狀態成功");
            } else {
                return new Result("0001", "變更狀態拒絕");
            }
        }

        return new Result("0001", "非可處理的活動狀態變更");

    }

}
  • 這裡我們只需要看一下程式碼實現的結構即可。從上到下是一整篇的ifelse,基本這也是大部分初級程式設計師的開發方式。
  • 這樣的程式導向式開發方式,對於不需要改動程式碼,也不需要二次迭代的,還是可以使用的(但基本不可能不迭代)。而且隨著狀態和需求變化,會越來越難以維護,後面的人也不好看懂並且很容易填充其他的流程進去。越來越亂就是從點滴開始的

3. 測試驗證

3.1 編寫測試類

@Test
public void test() {
    // 初始化資料
    String activityId = "100001";
    ActivityService.init(activityId, Status.Editing);  

    ActivityExecStatusController activityExecStatusController = new ActivityExecStatusController();
    Result resultRefuse = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Refuse); 
    logger.info("測試結果(編輯中To稽核拒絕):{}", JSON.toJSONString(resultRefuse));                           

    Result resultCheck = activityExecStatusController.execStatus(activityId, Status.Editing, Status.Check);
    logger.info("測試結果(編輯中To提交稽核):{}", JSON.toJSONString(resultCheck));
}
  • 我們的測試程式碼包括了兩個功能的驗證,一個是從編輯中稽核拒絕,另外一個是從編輯中到提交稽核
  • 因為從我們的場景流程中可以看到,編輯中的活動是不能直接到稽核拒絕的,這中間還需要提審

3.2 測試結果

23:24:30.774 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(編輯中To稽核拒絕):{"code":"0001","info":"變更狀態拒絕"}
23:24:30.778 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(編輯中To提交稽核):{"code":"0000","info":"變更狀態成功"}

Process finished with exit code 0
  • 從測試結果和我們的狀態流程的流轉中可以看到,是符合測試結果預期的。除了不好維護外,這樣的開發過程還是蠻快的,但不建議這麼搞!

六、狀態模式重構程式碼

接下來使用狀態模式來進行程式碼優化,也算是一次很小的重構。

重構的重點往往是處理掉ifelse,而想處理掉ifelse基本離不開介面抽象類,另外還需要重新改造程式碼結構。

1. 工程結構

itstack-demo-design-19-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── event
                │    ├── CheckState.java
                │    └── CloseState.java
                │    └── DoingState.java
                │    └── EditingState.java
                │    └── OpenState.java
                │    └── PassState.java
                │    └── RefuseState.java
                ├── Result.java
                ├── State.java
                └── StateHandler.java

狀態模式模型結構

狀態模式模型結構

  • 以上是狀態模式的整個工程結構模型,State是一個抽象類,定義了各種操作介面(提審、稽核、拒審等)。
  • 右側的不同顏色狀態與我們場景模擬中的顏色保持一致,是各種狀態流程流轉的實現操作。這裡的實現有一個關鍵點就是每一種狀態到下一個狀態,都分配到各個實現方法中控制,也就不需要if語言進行判斷了。
  • 最後是StateHandler對狀態流程的統一處理,裡面提供Map結構的各項服務介面呼叫,也就避免了使用if判斷各項狀態轉變的流程。

2. 程式碼實現

2.1 定義狀態抽象類

public abstract class State {

    /**
     * 活動提審
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result arraignment(String activityId, Enum<Status> currentStatus);

    /**
     * 稽核通過
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result checkPass(String activityId, Enum<Status> currentStatus);

    /**
     * 稽核拒絕
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result checkRefuse(String activityId, Enum<Status> currentStatus);

    /**
     * 撤審撤銷
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result checkRevoke(String activityId, Enum<Status> currentStatus);

    /**
     * 活動關閉
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result close(String activityId, Enum<Status> currentStatus);

    /**
     * 活動開啟
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result open(String activityId, Enum<Status> currentStatus);

    /**
     * 活動執行
     *
     * @param activityId    活動ID
     * @param currentStatus 當前狀態
     * @return 執行結果
     */
    public abstract Result doing(String activityId, Enum<Status> currentStatus);

}
  • 在整個介面中提供了各項狀態流轉服務的介面,例如;活動提審、稽核通過、稽核拒絕、撤審撤銷等7個方法。
  • 在這些方法中所有的入參都是一樣的,activityId(活動ID)、currentStatus(當前狀態),只有他們的具體實現是不同的。

2.2 部分狀態流轉實現

編輯

public class EditingState extends State {

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Check);
        return new Result("0000", "活動提審成功");
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "編輯中不可稽核通過");
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "編輯中不可稽核拒絕");
    }

    @Override
    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "編輯中不可撤銷稽核");
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Close);
        return new Result("0000", "活動關閉成功");
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "非關閉活動不可開啟");
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "編輯中活動不可執行活動中變更");
    }

}

提審

public class CheckState extends State {

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "待稽核狀態不可重複提審");
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Pass);
        return new Result("0000", "活動稽核通過完成");
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Refuse);
        return new Result("0000", "活動稽核拒絕完成");
    }

    @Override
    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Editing);
        return new Result("0000", "活動稽核撤銷回到編輯中");
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        ActivityService.execStatus(activityId, currentStatus, Status.Close);
        return new Result("0000", "活動稽核關閉完成");
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "非關閉活動不可開啟");
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return new Result("0001", "待稽核活動不可執行活動中變更");
    }

}
  • 這裡提供了兩個具體實現類的內容,編輯狀態和提審狀態。
  • 例如在這兩個實現類中,checkRefuse這個方法對於不同的類中有不同的實現,也就是不同狀態下能做的下一步流轉操作已經可以在每一個方法中具體控制了。
  • 其他5個類的操作是類似的具體就不在這裡演示了,大部分都是重複程式碼。可以通過原始碼進行學習理解。

2.3 狀態處理服務

public class StateHandler {

    private Map<Enum<Status>, State> stateMap = new ConcurrentHashMap<Enum<Status>, State>();

    public StateHandler() {
        stateMap.put(Status.Check, new CheckState());     // 待稽核
        stateMap.put(Status.Close, new CloseState());     // 已關閉
        stateMap.put(Status.Doing, new DoingState());     // 活動中
        stateMap.put(Status.Editing, new EditingState()); // 編輯中
        stateMap.put(Status.Open, new OpenState());       // 已開啟
        stateMap.put(Status.Pass, new PassState());       // 稽核通過
        stateMap.put(Status.Refuse, new RefuseState());   // 稽核拒絕
    }

    public Result arraignment(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).arraignment(activityId, currentStatus);
    }

    public Result checkPass(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkPass(activityId, currentStatus);
    }

    public Result checkRefuse(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkRefuse(activityId, currentStatus);
    }

    public Result checkRevoke(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).checkRevoke(activityId, currentStatus);
    }

    public Result close(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).close(activityId, currentStatus);
    }

    public Result open(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).open(activityId, currentStatus);
    }

    public Result doing(String activityId, Enum<Status> currentStatus) {
        return stateMap.get(currentStatus).doing(activityId, currentStatus);
    }
    
}
  • 這是對狀態服務的統一控制中心,可以看到在建構函式中提供了所有狀態和實現的具體關聯,放到Map資料結構中。
  • 同時提供了不同名稱的介面操作類,讓外部呼叫方可以更加容易的使用此項功能介面,而不需要像在itstack-demo-design-19-01例子中還得傳兩個狀態來判斷。

3. 測試驗證

3.1 編寫測試類(Editing2Arraignment)

@Test
public void test_Editing2Arraignment() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Editing);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.arraignment(activityId, Status.Editing);
    logger.info("測試結果(編輯中To提審活動):{}", JSON.toJSONString(result));
    logger.info("活動資訊:{} 狀態:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

測試結果

23:59:20.883 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(編輯中To提審活動):{"code":"0000","info":"活動提審成功"}
23:59:20.907 [main] INFO  org.itstack.demo.design.test.ApiTest - 活動資訊:{"activityId":"100001","activityName":"早起學習打卡領獎活動","beginTime":1593694760892,"endTime":1593694760892,"status":"Check"} 狀態:"Check"

Process finished with exit code 0
  • 測試編輯中To提審活動,的狀態流轉。

3.2 編寫測試類(Editing2Open)

@Test
public void test_Editing2Open() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Editing);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.open(activityId, Status.Editing);
    logger.info("測試結果(編輯中To開啟活動):{}", JSON.toJSONString(result));
    logger.info("活動資訊:{} 狀態:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

測試結果

23:59:36.904 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(編輯中To開啟活動):{"code":"0001","info":"非關閉活動不可開啟"}
23:59:36.914 [main] INFO  org.itstack.demo.design.test.ApiTest - 活動資訊:{"activityId":"100001","activityName":"早起學習打卡領獎活動","beginTime":1593694776907,"endTime":1593694776907,"status":"Editing"} 狀態:"Editing"

Process finished with exit code 0
  • 測試編輯中To開啟活動,的狀態流轉。

3.3 編寫測試類(Refuse2Doing)

@Test
public void test_Refuse2Doing() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Refuse);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.doing(activityId, Status.Refuse);
    logger.info("測試結果(拒絕To活動中):{}", JSON.toJSONString(result));
    logger.info("活動資訊:{} 狀態:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

測試結果

23:59:46.339 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(拒絕To活動中):{"code":"0001","info":"稽核拒絕不可執行活動為進行中"}
23:59:46.352 [main] INFO  org.itstack.demo.design.test.ApiTest - 活動資訊:{"activityId":"100001","activityName":"早起學習打卡領獎活動","beginTime":1593694786342,"endTime":1593694786342,"status":"Refuse"} 狀態:"Refuse"

Process finished with exit code 0
  • 測試拒絕To活動中,的狀態流轉。

3.4 編寫測試類(Refuse2Revoke)

@Test
public void test_Refuse2Revoke() {
    String activityId = "100001";
    ActivityService.init(activityId, Status.Refuse);
    StateHandler stateHandler = new StateHandler();
    Result result = stateHandler.checkRevoke(activityId, Status.Refuse);
    logger.info("測試結果(拒絕To撤審):{}", JSON.toJSONString(result));
    logger.info("活動資訊:{} 狀態:{}", JSON.toJSONString(ActivityService.queryActivityInfo(activityId)), JSON.toJSONString(ActivityService.queryActivityInfo(activityId).getStatus()));
}

測試結果

23:59:50.197 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果(拒絕To撤審):{"code":"0000","info":"撤銷稽核完成"}
23:59:50.208 [main] INFO  org.itstack.demo.design.test.ApiTest - 活動資訊:{"activityId":"100001","activityName":"早起學習打卡領獎活動","beginTime":1593694810201,"endTime":1593694810201,"status":"Editing"} 狀態:"Editing"

Process finished with exit code 0
  • 測試測試結果(拒絕To撤審),的狀態流轉。

  • 綜上以上四個測試類分別模擬了不同狀態之間的有效流轉拒絕流轉,不同的狀態服務處理不同的服務內容。

七、總結

  • 從以上的兩種方式對一個需求的實現中可以看到,在第二種使用設計模式處理後已經沒有了ifelse,程式碼的結構也更加清晰易於擴充套件。這就是設計模式的好處,可以非常強大的改變原有程式碼的結構,讓以後的擴充套件和維護都變得容易些。
  • 在實現結構的編碼方式上可以看到這不再是程式導向的程式設計,而是物件導向的結構。並且這樣的設計模式滿足了單一職責開閉原則,當你只有滿足這樣的結構下才會發現程式碼的擴充套件是容易的,也就是增加和修改功能不會影響整體的變化。
  • 但如果狀態和各項流轉較多像本文的案例中,就會產生較多的實現類。因此可能也會讓程式碼的實現上帶來了時間成本,因為如果遇到這樣的場景可以按需評估投入回報率。主要點在於看是否經常修改、是否可以做成元件化、抽離業務與非業務功能。

八、推薦閱讀

相關文章