1. 程式人生 > >設計模式之狀態模式實戰

設計模式之狀態模式實戰

[本文原文連結地址:http://nullpointer.pw/design-patterns-state.html](http://nullpointer.pw/design-patterns-state.html) 本文以運營活動狀態轉換為例,結合 Spring 演示狀態模式的實踐應用。 型別:行為型模式 意圖:允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類。 主要解決:一個物件存在多個狀態,每個狀態行為不同,狀態可以相互轉換。 使用場景:1、行為隨狀態改變而改變的場景。 2、減少 switch..case 以及 if...else [設計模式系列文章目錄 ](http://nullpointer.pw/design-patterns.html) ## 角色 - State:抽象狀態角色,負責物件狀態定義,並且封裝環境角色以實現狀態切換。 - Context:環境角色,定義客戶端需要的介面,並且負責具體狀態的切換。 - ConcreteState:具體狀態角色,當前狀態要做的事情,以及當前狀態如何轉換其他狀態。 ## UML ![](http://www.plantuml.com/plantuml/svg/LSx12O0m40J0_rLnJqNIWOXW2LPmnWCVCM9k1K7iNIKe-DxPPLk396np81_4ZBibGdVmGSHSST9rKqDHqaaaWo691sVQGw0tVDmaSGoQsJVaaq9VzIJl-EAQtAQSrixzFRKQn_lK1G00) ## 實戰 本文以運營活動狀態為例,結合 Spring 演示狀態模式的實踐應用。 運營活動建立初始狀態為草稿狀態,編輯好活動之後,運營會後臺啟用活動,此時活動狀態為已啟用; 當達到活動開始時間時,定時任務會將活動狀態置為進行中; 當達到活動結束時間時,定時任務會將活動狀態置為已結束。 進行中的活動也可能會因為某些原因需要手動停用,此時活動狀態置為已停用。 狀態之間有著嚴格的前置校驗,比如草稿狀態可以繼續儲存為草稿,也可以進行啟動,但不能直接切換為進行中,可以直接編輯切換回草稿箱狀態;比如已停用的狀態只有在啟用之後才能被置為進行中。 活動狀態的切換約束如下圖: | 新狀態→
當前狀態↓ | 草稿箱 | 已啟用 | 進行中 | 已停用 | 已結束 | | :---------------------: | :----: | :----: | :----: | :----: | :----: | | 草稿箱 | ✅ | ✅ | ❌ | ❌ | ❌ | | 已啟用 | ✅ | ❌ | ✅ | ✅ | ❌ | | 進行中 | ❌ | ❌ | ❌ | ✅ | ✅ | | 已停用 | ❌ | ✅ | ❌ | ❌ | ❌ | | 已結束 | ❌ | ❌ | ❌ | ❌ | ❌ | 如果不採取狀態模式,可能寫出的程式碼就是不斷使用 if 判斷前置狀態是否符合規則,當增加了新的狀態,需要改動判斷的地方,從而可能引入了 Bug。 ### 本文示例 UML 圖 ![](http://www.plantuml.com/plantuml/svg/vLR1Ri8m3BtxAonnwJJr2q0Jqs3Ipdo1svejKabGOofGuTyfGOMswhAC8pqcUNxnUtQipArG8RjD3fHOAIWLJ7Eo5jzJKQMImqf862k0oMthmsZXlI1rrm28hrWQbQ5bwO6ZFu9VN73L977wgdU_kK0vR3dg7oR6v4mQBPMyA6XzPx_H_XR2cfASm_7Edd1ufkp_-DTAA_ipX3y1T2lHE5VLL76lFjHUJGSBuOuYJzWrnM1lmnmDyZ7GlGSySmgQ5hvm3FJLSobUkkH69NbrkNPKYXTNnb5f_dJG9vSC_M5luhVkg8Vk19yTutXWslbGhGdXWzrVbVVALYVwSAtgmPnrLlyupW00) ## 示例程式碼 ### 定義抽象狀態角色 ```java public abstract class ActivityState { // 抽象狀態角色需要持有環境上下文物件 protected ActivityContext activityContext; public void setActivityContext(ActivityContext activityContext) { this.activityContext = activityContext; } public abstract Integer type(); /** * 判斷是否是當前狀態 */ protected boolean isSameStatus(Activity activity) { return type().equals(activity.getStatus()); } /** * 儲存草稿 */ public abstract boolean saveDraft(Activity activity); /** * 啟用 */ public abstract boolean enable(Activity activity); /** * 開始 */ public abstract boolean start(Activity activity); /** * 停用 */ public abstract boolean disable(Activity activity); /** * 停止 */ public abstract boolean finish(Activity activity); } ``` ### 定義環境角色 ```java public class ActivityContext { // 持有抽象狀態角色引用 private ActivityState activityState; public void setActivityState(ActivityState activityState) { this.activityState = activityState; this.activityState.setActivityContext(this); } public boolean saveDraft(Activity activity) { // 委託具體的狀態角色 return this.activityState.saveDraft(activity); } public boolean enable(Activity activity) { return this.activityState.enable(activity); } public boolean start(Activity activity) { return this.activityState.start(activity); } public boolean disable(Activity activity) { return this.activityState.disable(activity); } public boolean finish(Activity activity) { return this.activityState.finish(activity); } } ``` ### 定義具體狀態角色 因為本文示例具體狀態角色有很多,因此只列舉一個開啟狀態角色舉例參考,更多程式碼可以參考本文對應的 GitHub 示例程式碼 ```java @Component public class ActivityEnableState extends ActivityState { @Resource private ActivityDraftState activityDraftState; @Resource private ActivityStartState activityStartState; @Resource private ActivityDisableState activityDisableState; @Override public Integer type() { return ActivityStateEnum.ENABLE.getCode(); } @Override public boolean saveDraft(Activity activity) { super.activityContext.setActivityState(activityDraftState); return activityContext.saveDraft(activity); } @Override public boolean enable(Activity activity) { // 如果當前狀態已經是 enable 了,則無法再次 enable if (isSameStatus(activity)) { return false; } activity.setStatus(type()); //TODO 更新資料庫 return true; } @Override public boolean start(Activity activity) { super.activityContext.setActivityState(activityStartState); return activityContext.start(activity); } @Override public boolean disable(Activity activity) { super.activityContext.setActivityState(activityDisableState); return activityContext.disable(activity); } @Override public boolean finish(Activity activity) { // 非進行中的活動狀態,不允許直接進行 finish return false; } } ``` ### 封裝具體狀態例項工廠 狀態角色應該是單例的,結合 Spring 與工廠模式對例項進行封裝,方便根據資料庫的 status 值獲取對應的狀態角色例項。 ```java @Component public class ActivityStateFactory implements ApplicationContextAware { public static f