设计模式之状态模式实战

本文原文连接地址: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
相关文章
相关标签/搜索