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

本文通过一个活动管理系统的案例,详细介绍了如何使用状态模式来简化复杂的业务逻辑判断,避免大量的if-else语句,提高代码的可读性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

功能背景

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

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

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

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

结束:活动已经结束了

存在问题

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

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个半...希望以后自己多写点文章吧...

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值