• 活动功能->状态模式的使用


    功能背景

            最近接到产品提出的需求,需求是要做一个活动管理的模块,管理人员在后台创建活动,然后门店可以加入活动,同时设置一些优惠商品,从而提升门店的商品消费量,具体的需求这里就不展开了,既然是活动那活动便有状态,活动的状态有以下的状态

    未开始:活动刚创建,还没有开始

    进行中:活动开始举行的状态

    暂停中:活动进入暂停状态

    结束:活动已经结束了

    存在问题

            如上所述,活动状态存在四种状态,那么在各个业务中就会涉及到可操作与不可状态的情况,比如说

    1. 如果一个活动要进入暂停状态,那么活动进入前就需要处于进行中才能操作

    2. 如果一个活动要进入进行中状态,那么活动进入前需要处于未开始或者暂停中才能操作

    3. 等等之类的业务操作....

    相关类定义

    1. package com.zxc.state.entity;
    2. import com.zxc.state.enums.ActivityStateEnum;
    3. import lombok.Data;
    4. /**
    5. * @author zxc_user
    6. * @date 2022/11/1 20:39
    7. * @version 1.0
    8. * @description 活动对象
    9. **/
    10. @Data
    11. public class Activity {
    12. /**
    13. * 活动id
    14. */
    15. private Integer id;
    16. /**
    17. * 活动名称
    18. */
    19. private String name;
    20. /**
    21. * 活动状态
    22. */
    23. private ActivityStateEnum activityState;
    24. }
    1. package com.zxc.state.enums;
    2. import lombok.Getter;
    3. @Getter
    4. public enum ActivityStateEnum {
    5. NOT_START(1, "未开始"),
    6. STARTING(2, "进行中"),
    7. PAUSE(3, "暂停中"),
    8. END(4, "已结束"),
    9. ;
    10. ActivityStateEnum(int code, String message) {
    11. this.code = code;
    12. this.message = message;
    13. }
    14. private final int code;
    15. private final String message;
    16. }

    问题解决方案

    传统方案

            以上状态的切换在我们平常的开发中,比较常用的就是直接判断活动所在的状态能不能操作,比如下面的两个业务方法

          

    1. package com.zxc.state.service;
    2. import com.zxc.state.entity.Activity;
    3. import com.zxc.state.enums.ActivityStateEnum;
    4. import java.util.Objects;
    5. public class ActivityService {
    6. /**
    7. * 开始活动
    8. * @param activityId
    9. */
    10. public void start(Integer activityId){
    11. //模拟获取活动
    12. Activity activity = new Activity();
    13. ActivityStateEnum activityState = activity.getActivityState();
    14. if(!Objects.equals(ActivityStateEnum.NOT_START, activityState) && !Objects.equals(ActivityStateEnum.PAUSE, activityState)) {
    15. throw new RuntimeException("活动既不处于未开始也不处于暂停中状态,无法开始活动");
    16. }
    17. //业务操作......
    18. }
    19. public void pause(Integer activityId) {
    20. //模拟获取活动
    21. Activity activity = new Activity();
    22. ActivityStateEnum activityState = activity.getActivityState();
    23. if(!Objects.equals(ActivityStateEnum.STARTING, activityState)) {
    24. throw new RuntimeException("活动既不处于进行中,无法暂停");
    25. }
    26. //业务操作.....
    27. }
    28. }

    以上逻辑很清晰,估计大家一看就懂,但是同时也存在很明显的问题,就是很多地方都需要if判断活动的状态来保证业务方法的正常运行,当然了,你可以适当封装方法,但是还是存在很多判断,下面就介绍一下我在项目中使用的方式,状态模式来解决

    状态模式方案

            如果不是很清楚状态模式的可以参考一下这篇文章,个人觉得讲的很不错,这里我是默认大家对状态模式都有一定理解的

     https://blog.csdn.net/weixin_47936614/article/details/125290833

           注:状态模式的实现有很多种,可能我这种未必跟别人一模一样,所以有出入也是正常的,下面开始正式介绍我在项目中使用的状态模式

    1. 首先需要定义一个接口,用于表示活动状态变更的所有业务状态,比如修改为暂停中,进行中等方法,具体定义如下

    1. package com.zxc.state.design;
    2. /**
    3. * 活动变更接口
    4. *
    5. * 注:之所以default是因为某些方法状态本身就不支持,可以不覆盖即可
    6. */
    7. public interface ActivityStateChange {
    8. /**
    9. * 暂停活动
    10. */
    11. default boolean pause(Integer activityId){
    12. throw new UnsupportedOperationException("不支持暂停操作");
    13. }
    14. /**
    15. * 开始活动
    16. */
    17. default boolean start(Integer activityId){
    18. throw new UnsupportedOperationException("不支持开始操作");
    19. }
    20. /**
    21. * 结束
    22. */
    23. default boolean end(Integer activityId){
    24. throw new UnsupportedOperationException("不支持结束操作");
    25. }
    26. }

    2. 一般情况下可能我们就直接提供实现类了,但是一般建议提供一个抽象类,有些公用的方法可以在抽象类实现,如下,这里只是举个简单的例子

    1. package com.zxc.state.design;
    2. import com.zxc.state.entity.Activity;
    3. import com.zxc.state.service.ActivityService;
    4. import javax.annotation.Resource;
    5. /**
    6. * 抽象类
    7. */
    8. public abstract class AbstractActivityStateChange implements ActivityStateChange {
    9. @Resource
    10. private ActivityService activityService;
    11. protected Activity get(Integer activityId) {
    12. return activityService.get(activityId);
    13. }
    14. }

    3. 接下来就是实现类,一般来说活动有多少个状态就需要有多少个实现类,也就是把判断逻辑转移到了每个实现类里面进行判断,理解这个步骤是理解状态模式的关键,简单说就是当活动处于某种状态的时候,其他业务是否可以执行,下面放两个实现类出来,其他的见后面的代码

    1. package com.zxc.state.design;
    2. import com.zxc.state.entity.Activity;
    3. import org.springframework.stereotype.Component;
    4. /**
    5. * 未开始状态类型,放于spring容器中,后面会有用
    6. */
    7. @Component
    8. public class NotStartActivityStateChange extends AbstractActivityStateChange {
    9. /**
    10. * 当活动处于未开始时不支持暂停操作
    11. *
    12. * 进行对应的处理或者直接不覆盖会抛出UnsupportedOperationException异常
    13. */
    14. @Override
    15. public boolean pause(Integer activityId) {
    16. throw new RuntimeException("当前活动处于未开始,不支持暂停...");
    17. }
    18. /**
    19. * 当活动处于未开始时支持开始操作
    20. */
    21. @Override
    22. public boolean start(Integer activityId) {
    23. Activity activity = get(activityId);
    24. //业务处理.....
    25. return true;
    26. }
    27. /**
    28. * 当活动处于未开始时支持结束操作,当然了,这个看你业务定义了
    29. */
    30. @Override
    31. public boolean end(Integer activityId) {
    32. Activity activity = get(activityId);
    33. //业务处理.....
    34. return true;
    35. }
    36. }
    1. package com.zxc.state.design;
    2. import com.zxc.state.entity.Activity;
    3. import org.springframework.stereotype.Component;
    4. /**
    5. * 进行中状态类型,放于spring容器中,后面会有用
    6. */
    7. @Component
    8. public class StartingActivityStateChange extends AbstractActivityStateChange {
    9. @Override
    10. public boolean pause(Integer activityId) {
    11. Activity activity = get(activityId);
    12. //业务处理.....
    13. return true;
    14. }
    15. /**
    16. * 当活动处于进行行不支持开始状态,不用覆盖即可,会抛出UnsupportedOperationException异常
    17. */
    18. // @Override
    19. // public boolean start(Integer activityId) {
    20. // Activity activity = get(activityId);
    21. // //业务处理.....
    22. // return true;
    23. // }
    24. /**
    25. * 当活动处于未开始时支持结束操作,当然了,这个看你业务定义了
    26. */
    27. @Override
    28. public boolean end(Integer activityId) {
    29. Activity activity = get(activityId);
    30. //业务处理.....
    31. return true;
    32. }
    33. }

    4. 接下来就是如何进行使用了,这里我是利用了spring的机制来处理的,提供了一个ActivityStateContext对象来维护状态的实现类,同时为了能做到扩展性,为ActivityStateChange添加一个接口,如下

    1. package com.zxc.state.design;
    2. import com.zxc.state.enums.ActivityStateEnum;
    3. /**
    4. * 活动变更接口
    5. *
    6. * 注:之所以default是因为某些方法状态本身就不支持,可以不覆盖即可
    7. */
    8. public interface ActivityStateChange {
    9. /**
    10. * 暂停活动
    11. */
    12. default boolean pause(Integer activityId){
    13. throw new UnsupportedOperationException("不支持暂停操作");
    14. }
    15. /**
    16. * 开始活动
    17. */
    18. default boolean start(Integer activityId){
    19. throw new UnsupportedOperationException("不支持开始操作");
    20. }
    21. /**
    22. * 结束
    23. */
    24. default boolean end(Integer activityId){
    25. throw new UnsupportedOperationException("不支持结束操作");
    26. }
    27. /**
    28. * 获取活动状态
    29. * @return
    30. */
    31. ActivityStateEnum getActivityState();
    32. }

    对应的抽象类也进行了对应的修改,如下

    1. package com.zxc.state.design;
    2. import com.zxc.state.entity.Activity;
    3. import com.zxc.state.enums.ActivityStateEnum;
    4. import com.zxc.state.service.ActivityService;
    5. import javax.annotation.Resource;
    6. /**
    7. * 抽象类
    8. */
    9. public abstract class AbstractActivityStateChange implements ActivityStateChange {
    10. @Resource
    11. private ActivityService activityService;
    12. protected Activity get(Integer activityId) {
    13. return activityService.get(activityId);
    14. }
    15. @Override
    16. public ActivityStateEnum getActivityState() {
    17. ActivityStateEnum activityStateEnum = doGetActivityState();
    18. if(activityStateEnum == null) {
    19. throw new RuntimeException("activityStateEnum不能为空");
    20. }
    21. return activityStateEnum;
    22. }
    23. protected abstract ActivityStateEnum doGetActivityState();
    24. }

    那么每个实现类自然就要实现doGetActivityState方法了,这里就不贴了,具体看代码,有了这些以后,就可以维护ActivityStateContext上下文了,如下

    1. package com.zxc.state.design;
    2. import com.zxc.state.enums.ActivityStateEnum;
    3. import org.springframework.beans.factory.InitializingBean;
    4. import org.springframework.stereotype.Component;
    5. import javax.annotation.Resource;
    6. import java.util.List;
    7. import java.util.Map;
    8. import java.util.concurrent.ConcurrentHashMap;
    9. @Component
    10. public class ActivityStateContext implements InitializingBean {
    11. /**
    12. * ActivityStateChange实现类列表
    13. */
    14. @Resource
    15. private List activityStateChangeList;
    16. private Map changeMap;
    17. @Override
    18. public void afterPropertiesSet() throws Exception {
    19. changeMap = new ConcurrentHashMap<>(activityStateChangeList.size());
    20. //组装map
    21. for (ActivityStateChange activityStateChange : activityStateChangeList) {
    22. if(changeMap.containsKey(activityStateChange.getActivityState())) {
    23. throw new RuntimeException("有重复的实现类,请检查!!!");
    24. }
    25. changeMap.put(activityStateChange.getActivityState(), activityStateChange);
    26. }
    27. }
    28. /**
    29. * 根据状态获取处理机
    30. * @param activityState
    31. * @return
    32. */
    33. public ActivityStateChange getActivityStateChange(ActivityStateEnum activityState) {
    34. ActivityStateChange activityStateChange = changeMap.get(activityState);
    35. if(activityState == null) {
    36. throw new RuntimeException("状态处理机不存在,请检查数据问题!");
    37. }
    38. return activityStateChange;
    39. }
    40. }

    5. 最后一步就是使用了,来看看现在是怎么使用的,如下

    1. package com.zxc.state.design;
    2. import com.zxc.state.entity.Activity;
    3. import com.zxc.state.enums.ActivityStateEnum;
    4. import org.springframework.stereotype.Service;
    5. import javax.annotation.Resource;
    6. @Service
    7. public class ActivityDesignService {
    8. @Resource
    9. private ActivityStateContext activityStateContext;
    10. /**
    11. * 开始活动
    12. * @param activityId
    13. */
    14. public void start(Integer activityId){
    15. //模拟获取活动
    16. Activity activity = new Activity();
    17. ActivityStateEnum activityState = activity.getActivityState();
    18. //直接开始活动,如果活动状态不支持,会直接抛出异常
    19. activityStateContext.getActivityStateChange(activityState).start(activityId);
    20. //业务操作......
    21. }
    22. public void pause(Integer activityId) {
    23. //模拟获取活动
    24. Activity activity = new Activity();
    25. ActivityStateEnum activityState = activity.getActivityState();
    26. //直接暂停活动,如果活动状态不支持,会直接抛出异常
    27. activityStateContext.getActivityStateChange(activityState).pause(activityId);
    28. //业务操作.....
    29. }
    30. }

    到此,状态模式就完成了,if判断就不见了,全部都变成了方法调用,这样的好处是方便管理,而且很容易扩展,当然了如果不熟悉可能理解起来有一定难度,不过多看几遍就懂了,其实最核心的就两件事

    1,声明接口用于表示业务对象(这里是活动)的所有业务操作

    2,提供每种状态下的活动实现类,在实现类中定义能不能支持这个业务操作

    扩展思路

            如果觉得调用还是太麻烦,也可以考虑用aop和注解来解决这个问题,这里只说一个大概的思路,首先定义一个注解里面指定调用的业务方法,其次提供一个获取activityId的借口,业务入参实现这个接口,然后在aop中获取参数中的接口类型,然后调用对应的方法即可

    代码位置

           链接:https://share.weiyun.com/pZu6E5Ky 密码:q7c7xe

    结语

            以前对于状态模式一直搞不明白,但是后面突然间就懂了,本人也不擅长写文章,不知道这篇写的咋样,如果有啥建议,希望大家多多给建议,这几年看了很多技术,但是很少用到真正的开发中,也是希望自己能更多的把所学知识用到实际开发中

           写这篇文章耗时1个半...希望以后自己多写点文章吧...

  • 相关阅读:
    总结一下Feign的知识点
    verilog 并行块实现
    国产操作系统银河麒麟v10 (SP1) Kylin-server 安装Elasticsearch7.6.1
    LIO-SAM 详读代码笔记--2.imuPreintegration
    [ 2024春节 Flink打卡 ] -- 理论基础
    tomcat部署
    ESP8266-Arduino编程实例-PCT2075温度数字转换器驱动
    maya blendshape
    python关键字
    R语言使用plot函数可视化数据散点图,使用lines函数在可视化图像中添加线条、使用lwd参数自定义线条的粗细
  • 原文地址:https://blog.csdn.net/zxc_user/article/details/127640848