設計模式之狀態模式實戰

本文原文連接地址:http://nullpointer.pw/design-patterns-state.htmlhtml

本文以運營活動狀態轉換爲例,結合 Spring 演示狀態模式的實踐應用。java

類型:行爲型模式git

意圖:容許對象在內部狀態發生改變時改變它的行爲,對象看起來好像修改了它的類。github

主要解決:一個對象存在多個狀態,每一個狀態行爲不一樣,狀態能夠相互轉換。算法

使用場景:一、行爲隨狀態改變而改變的場景。 二、減小 switch..case 以及 if...else數據庫

設計模式系列文章目錄設計模式

角色

  • State:抽象狀態角色,負責對象狀態定義,而且封裝環境角色以實現狀態切換。app

  • Context:環境角色,定義客戶端須要的接口,而且負責具體狀態的切換。ide

  • ConcreteState:具體狀態角色,當前狀態要作的事情,以及當前狀態如何轉換其餘狀態。測試

UML

實戰

本文以運營活動狀態爲例,結合 Spring 演示狀態模式的實踐應用。

運營活動建立初始狀態爲草稿狀態,編輯好活動以後,運營會後臺啓用活動,此時活動狀態爲已啓用;
當達到活動開始時間時,定時任務會將活動狀態置爲進行中;
當達到活動結束時間時,定時任務會將活動狀態置爲已結束。
進行中的活動也可能會由於某些緣由須要手動停用,此時活動狀態置爲已停用。

狀態之間有着嚴格的前置校驗,好比草稿狀態能夠繼續保存爲草稿,也能夠進行啓動,但不能直接切換爲進行中,能夠直接編輯切換回草稿箱狀態;好比已停用的狀態只有在啓用以後才能被置爲進行中。

活動狀態的切換約束以下圖:

新狀態→
當前狀態↓
草稿箱 已啓用 進行中 已停用 已結束
草稿箱
已啓用
進行中
已停用
已結束

若是不採起狀態模式,可能寫出的代碼就是不斷使用 if 判斷前置狀態是否符合規則,當增長了新的狀態,須要改動判斷的地方,從而可能引入了 Bug。

本文示例 UML 圖

示例代碼

定義抽象狀態角色

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);
}

定義環境角色

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 示例代碼

@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 值獲取對應的狀態角色實例。

@Component
public class ActivityStateFactory implements ApplicationContextAware {
    public static final Map<Integer, ActivityState> STATE_MAP = new HashMap<>(ActivityStateEnum.values().length);

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, ActivityState> beans = applicationContext.getBeansOfType(ActivityState.class);
        beans.values().forEach(item -> STATE_MAP.put(item.type(), item));
    }
}

測試

@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class BaseTest {


    @Test
    public void test1() {
        // 通常活動都是從數據庫查詢出來了,此處爲方便測試直接 new
        Activity activity = new Activity()
                .setId(1L)
                .setName("測試活動")
                .setStatus(ActivityStateEnum.DRAFT.getCode())
                .setCreateTime(LocalDateTime.now());

        ActivityState activityState = ActivityStateFactory.STATE_MAP.get(activity.getStatus());
        ActivityContext context = new ActivityContext();
        context.setActivityState(activityState);

        System.out.println("保存草稿: " + (context.saveDraft(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲已啓用: " + (context.enable(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲進行中: " + (context.start(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲已停用: " + (context.disable(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲已啓用: " + (context.enable(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲進行中: " + (context.start(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲已結束: " + (context.finish(activity) ? "成功" : "失敗"));
        System.out.println("更新活動狀態爲進行中: " + (context.start(activity) ? "成功" : "失敗"));
    }
}

結果輸出:

保存草稿: 成功
更新活動狀態爲已啓用: 成功
更新活動狀態爲進行中: 成功
更新活動狀態爲已停用: 成功
更新活動狀態爲已啓用: 成功
更新活動狀態爲進行中: 成功
更新活動狀態爲已結束: 成功
更新活動狀態爲進行中: 失敗

能夠看到狀態切換路徑:草稿-> 草稿-> 已啓用-> 進行中-> 已停用-> 已啓用-> 進行中-> 已結束-> 進行中,前面都是正確切換,可是已結束沒法切換爲進行中狀態,從而驗證了狀態模式的應用。

總結

看上一篇策略模式的文章中的 UML,和本文的 UML 是相同的。那麼他們的區別是什麼呢?
策略模式是提供了可相互替換的算法,根據客戶端選擇一種算法指定一種行爲;
狀態模式則包含了對象狀態,根據對象狀態不一樣,行爲也不同,即狀態決定行爲,將行爲對應的邏輯封裝到具體狀態類中,在環境類中消除邏輯判斷,且具體實現不可相互替換。

狀態模式中,客戶端角色與狀態對象不須要進行交互,全部的交互都委託給環境角色進行。

源碼下載

參考

  • https://www.runoob.com/design-pattern/state-pattern.html
  • https://www.cnblogs.com/kubixuesheng/p/5180509.html
  • https://www.kancloud.cn/sstd521/design/193606
相關文章
相關標籤/搜索