最近接到产品提出的需求,需求是要做一个活动管理的模块,管理人员在后台创建活动,然后门店可以加入活动,同时设置一些优惠商品,从而提升门店的商品消费量,具体的需求这里就不展开了,既然是活动那活动便有状态,活动的状态有以下的状态
未开始:活动刚创建,还没有开始
进行中:活动开始举行的状态
暂停中:活动进入暂停状态
结束:活动已经结束了
如上所述,活动状态存在四种状态,那么在各个业务中就会涉及到可操作与不可状态的情况,比如说
1. 如果一个活动要进入暂停状态,那么活动进入前就需要处于进行中才能操作
2. 如果一个活动要进入进行中状态,那么活动进入前需要处于未开始或者暂停中才能操作
3. 等等之类的业务操作....
- package com.zxc.state.entity;
-
- import com.zxc.state.enums.ActivityStateEnum;
- import lombok.Data;
-
- /**
- * @author zxc_user
- * @date 2022/11/1 20:39
- * @version 1.0
- * @description 活动对象
- **/
- @Data
- public class Activity {
- /**
- * 活动id
- */
- private Integer id;
- /**
- * 活动名称
- */
- private String name;
- /**
- * 活动状态
- */
- private ActivityStateEnum activityState;
- }
- package com.zxc.state.enums;
-
- import lombok.Getter;
-
- @Getter
- public enum ActivityStateEnum {
-
-
- NOT_START(1, "未开始"),
- STARTING(2, "进行中"),
- PAUSE(3, "暂停中"),
- END(4, "已结束"),
-
-
- ;
-
- ActivityStateEnum(int code, String message) {
- this.code = code;
- this.message = message;
- }
-
- private final int code;
- private final String message;
- }
以上状态的切换在我们平常的开发中,比较常用的就是直接判断活动所在的状态能不能操作,比如下面的两个业务方法
- package com.zxc.state.service;
-
- import com.zxc.state.entity.Activity;
- import com.zxc.state.enums.ActivityStateEnum;
-
- import java.util.Objects;
-
- public class ActivityService {
- /**
- * 开始活动
- * @param activityId
- */
- public void start(Integer activityId){
- //模拟获取活动
- Activity activity = new Activity();
- ActivityStateEnum activityState = activity.getActivityState();
- if(!Objects.equals(ActivityStateEnum.NOT_START, activityState) && !Objects.equals(ActivityStateEnum.PAUSE, activityState)) {
- throw new RuntimeException("活动既不处于未开始也不处于暂停中状态,无法开始活动");
- }
- //业务操作......
- }
-
- public void pause(Integer activityId) {
- //模拟获取活动
- Activity activity = new Activity();
- ActivityStateEnum activityState = activity.getActivityState();
- if(!Objects.equals(ActivityStateEnum.STARTING, activityState)) {
- throw new RuntimeException("活动既不处于进行中,无法暂停");
- }
- //业务操作.....
- }
- }
以上逻辑很清晰,估计大家一看就懂,但是同时也存在很明显的问题,就是很多地方都需要if判断活动的状态来保证业务方法的正常运行,当然了,你可以适当封装方法,但是还是存在很多判断,下面就介绍一下我在项目中使用的方式,状态模式来解决
如果不是很清楚状态模式的可以参考一下这篇文章,个人觉得讲的很不错,这里我是默认大家对状态模式都有一定理解的
https://blog.csdn.net/weixin_47936614/article/details/125290833
注:状态模式的实现有很多种,可能我这种未必跟别人一模一样,所以有出入也是正常的,下面开始正式介绍我在项目中使用的状态模式
1. 首先需要定义一个接口,用于表示活动状态变更的所有业务状态,比如修改为暂停中,进行中等方法,具体定义如下
- package com.zxc.state.design;
-
- /**
- * 活动变更接口
- *
- * 注:之所以default是因为某些方法状态本身就不支持,可以不覆盖即可
- */
- public interface ActivityStateChange {
- /**
- * 暂停活动
- */
- default boolean pause(Integer activityId){
- throw new UnsupportedOperationException("不支持暂停操作");
- }
-
- /**
- * 开始活动
- */
- default boolean start(Integer activityId){
- throw new UnsupportedOperationException("不支持开始操作");
- }
-
- /**
- * 结束
- */
- default boolean end(Integer activityId){
- throw new UnsupportedOperationException("不支持结束操作");
- }
- }
2. 一般情况下可能我们就直接提供实现类了,但是一般建议提供一个抽象类,有些公用的方法可以在抽象类实现,如下,这里只是举个简单的例子
- package com.zxc.state.design;
-
- import com.zxc.state.entity.Activity;
- import com.zxc.state.service.ActivityService;
-
- import javax.annotation.Resource;
-
- /**
- * 抽象类
- */
- public abstract class AbstractActivityStateChange implements ActivityStateChange {
-
- @Resource
- private ActivityService activityService;
- protected Activity get(Integer activityId) {
- return activityService.get(activityId);
- }
-
- }
3. 接下来就是实现类,一般来说活动有多少个状态就需要有多少个实现类,也就是把判断逻辑转移到了每个实现类里面进行判断,理解这个步骤是理解状态模式的关键,简单说就是当活动处于某种状态的时候,其他业务是否可以执行,下面放两个实现类出来,其他的见后面的代码
- package com.zxc.state.design;
-
- import com.zxc.state.entity.Activity;
- import org.springframework.stereotype.Component;
-
- /**
- * 未开始状态类型,放于spring容器中,后面会有用
- */
- @Component
- public class NotStartActivityStateChange extends AbstractActivityStateChange {
-
- /**
- * 当活动处于未开始时不支持暂停操作
- *
- * 进行对应的处理或者直接不覆盖会抛出UnsupportedOperationException异常
- */
- @Override
- public boolean pause(Integer activityId) {
- throw new RuntimeException("当前活动处于未开始,不支持暂停...");
- }
- /**
- * 当活动处于未开始时支持开始操作
- */
- @Override
- public boolean start(Integer activityId) {
- Activity activity = get(activityId);
- //业务处理.....
- return true;
- }
- /**
- * 当活动处于未开始时支持结束操作,当然了,这个看你业务定义了
- */
- @Override
- public boolean end(Integer activityId) {
- Activity activity = get(activityId);
- //业务处理.....
- return true;
- }
- }
- package com.zxc.state.design;
-
- import com.zxc.state.entity.Activity;
- import org.springframework.stereotype.Component;
-
- /**
- * 进行中状态类型,放于spring容器中,后面会有用
- */
- @Component
- public class StartingActivityStateChange extends AbstractActivityStateChange {
-
-
- @Override
- public boolean pause(Integer activityId) {
- Activity activity = get(activityId);
- //业务处理.....
- return true;
- }
- /**
- * 当活动处于进行行不支持开始状态,不用覆盖即可,会抛出UnsupportedOperationException异常
- */
- // @Override
- // public boolean start(Integer activityId) {
- // Activity activity = get(activityId);
- // //业务处理.....
- // return true;
- // }
- /**
- * 当活动处于未开始时支持结束操作,当然了,这个看你业务定义了
- */
- @Override
- public boolean end(Integer activityId) {
- Activity activity = get(activityId);
- //业务处理.....
- return true;
- }
- }
4. 接下来就是如何进行使用了,这里我是利用了spring的机制来处理的,提供了一个ActivityStateContext对象来维护状态的实现类,同时为了能做到扩展性,为ActivityStateChange添加一个接口,如下
- package com.zxc.state.design;
-
- import com.zxc.state.enums.ActivityStateEnum;
-
- /**
- * 活动变更接口
- *
- * 注:之所以default是因为某些方法状态本身就不支持,可以不覆盖即可
- */
- public interface ActivityStateChange {
- /**
- * 暂停活动
- */
- default boolean pause(Integer activityId){
- throw new UnsupportedOperationException("不支持暂停操作");
- }
-
- /**
- * 开始活动
- */
- default boolean start(Integer activityId){
- throw new UnsupportedOperationException("不支持开始操作");
- }
-
- /**
- * 结束
- */
- default boolean end(Integer activityId){
- throw new UnsupportedOperationException("不支持结束操作");
- }
-
- /**
- * 获取活动状态
- * @return
- */
- ActivityStateEnum getActivityState();
- }
对应的抽象类也进行了对应的修改,如下
- package com.zxc.state.design;
-
- import com.zxc.state.entity.Activity;
- import com.zxc.state.enums.ActivityStateEnum;
- import com.zxc.state.service.ActivityService;
-
- import javax.annotation.Resource;
-
- /**
- * 抽象类
- */
- public abstract class AbstractActivityStateChange implements ActivityStateChange {
-
- @Resource
- private ActivityService activityService;
- protected Activity get(Integer activityId) {
- return activityService.get(activityId);
- }
-
- @Override
- public ActivityStateEnum getActivityState() {
- ActivityStateEnum activityStateEnum = doGetActivityState();
- if(activityStateEnum == null) {
- throw new RuntimeException("activityStateEnum不能为空");
- }
- return activityStateEnum;
- }
-
- protected abstract ActivityStateEnum doGetActivityState();
- }
那么每个实现类自然就要实现doGetActivityState方法了,这里就不贴了,具体看代码,有了这些以后,就可以维护ActivityStateContext上下文了,如下
- package com.zxc.state.design;
-
- import com.zxc.state.enums.ActivityStateEnum;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.Resource;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
-
- @Component
- public class ActivityStateContext implements InitializingBean {
- /**
- * ActivityStateChange实现类列表
- */
- @Resource
- private List
activityStateChangeList; -
- private Map
changeMap; -
- @Override
- public void afterPropertiesSet() throws Exception {
- changeMap = new ConcurrentHashMap<>(activityStateChangeList.size());
- //组装map
- for (ActivityStateChange activityStateChange : activityStateChangeList) {
- if(changeMap.containsKey(activityStateChange.getActivityState())) {
- throw new RuntimeException("有重复的实现类,请检查!!!");
- }
- changeMap.put(activityStateChange.getActivityState(), activityStateChange);
- }
- }
-
- /**
- * 根据状态获取处理机
- * @param activityState
- * @return
- */
- public ActivityStateChange getActivityStateChange(ActivityStateEnum activityState) {
- ActivityStateChange activityStateChange = changeMap.get(activityState);
- if(activityState == null) {
- throw new RuntimeException("状态处理机不存在,请检查数据问题!");
- }
- return activityStateChange;
- }
- }
5. 最后一步就是使用了,来看看现在是怎么使用的,如下
- package com.zxc.state.design;
-
- import com.zxc.state.entity.Activity;
- import com.zxc.state.enums.ActivityStateEnum;
- import org.springframework.stereotype.Service;
-
- import javax.annotation.Resource;
-
- @Service
- public class ActivityDesignService {
- @Resource
- private ActivityStateContext activityStateContext;
- /**
- * 开始活动
- * @param activityId
- */
- public void start(Integer activityId){
- //模拟获取活动
- Activity activity = new Activity();
- ActivityStateEnum activityState = activity.getActivityState();
- //直接开始活动,如果活动状态不支持,会直接抛出异常
- activityStateContext.getActivityStateChange(activityState).start(activityId);
- //业务操作......
- }
-
- public void pause(Integer activityId) {
- //模拟获取活动
- Activity activity = new Activity();
- ActivityStateEnum activityState = activity.getActivityState();
- //直接暂停活动,如果活动状态不支持,会直接抛出异常
- activityStateContext.getActivityStateChange(activityState).pause(activityId);
- //业务操作.....
- }
- }
到此,状态模式就完成了,if判断就不见了,全部都变成了方法调用,这样的好处是方便管理,而且很容易扩展,当然了如果不熟悉可能理解起来有一定难度,不过多看几遍就懂了,其实最核心的就两件事
1,声明接口用于表示业务对象(这里是活动)的所有业务操作
2,提供每种状态下的活动实现类,在实现类中定义能不能支持这个业务操作
如果觉得调用还是太麻烦,也可以考虑用aop和注解来解决这个问题,这里只说一个大概的思路,首先定义一个注解里面指定调用的业务方法,其次提供一个获取activityId的借口,业务入参实现这个接口,然后在aop中获取参数中的接口类型,然后调用对应的方法即可
链接:https://share.weiyun.com/pZu6E5Ky 密码:q7c7xe
以前对于状态模式一直搞不明白,但是后面突然间就懂了,本人也不擅长写文章,不知道这篇写的咋样,如果有啥建议,希望大家多多给建议,这几年看了很多技术,但是很少用到真正的开发中,也是希望自己能更多的把所学知识用到实际开发中
写这篇文章耗时1个半...希望以后自己多写点文章吧...