• 设计模式之状态模式实战


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

    本文以运营活动状态转换为例,结合 Spring 演示状态模式的实践应用。

    类型:行为型模式

    意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

    主要解决:一个对象存在多个状态,每个状态行为不同,状态可以相互转换。

    使用场景:1、行为随状态改变而改变的场景。 2、减少 switch..case 以及 if...else

    设计模式系列文章目录

    角色

    • State:抽象状态角色,负责对象状态定义,并且封装环境角色以实现状态切换。

    • Context:环境角色,定义客户端需要的接口,并且负责具体状态的切换。

    • 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 是相同的。那么他们的区别是什么呢?
    策略模式是提供了可相互替换的算法,根据客户端选择一种算法指定一种行为;
    状态模式则包含了对象状态,根据对象状态不同,行为也不一样,即状态决定行为,将行为对应的逻辑封装到具体状态类中,在环境类中消除逻辑判断,且具体实现不可相互替换。

    状态模式中,客户端角色与状态对象不需要进行交互,所有的交互都委托给环境角色进行。

    源码下载

    参考

  • 相关阅读:
    【JLOI2011】飞行路线
    P3369 【模板】普通平衡树
    P1144 最短路计数
    P1462 通往奥格瑞玛的道路
    【NOIP2017】宝藏
    P1120 小木棍
    P3919 【模板】可持久化数组(可持久化线段树/平衡树)
    P3834 【模板】可持久化线段树 1(主席树)
    矩阵清零--进军硅谷
    二维数组搜素--进军硅谷
  • 原文地址:https://www.cnblogs.com/vcmq/p/12542374.html
Copyright © 2020-2023  润新知