功能背景
最近接到产品提出的需求,需求是要做一个活动管理的模块,管理人员在后台创建活动,然后门店可以加入活动,同时设置一些优惠商品,从而提升门店的商品消费量,具体的需求这里就不展开了,既然是活动那活动便有状态,活动的状态有以下的状态
未开始:活动刚创建,还没有开始
进行中:活动开始举行的状态
暂停中:活动进入暂停状态
结束:活动已经结束了
存在问题
如上所述,活动状态存在四种状态,那么在各个业务中就会涉及到可操作与不可状态的情况,比如说
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.youkuaiyun.com/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<ActivityStateChange> activityStateChangeList;
private Map<ActivityStateEnum, ActivityStateChange> 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个半...希望以后自己多写点文章吧...