中奖记录设计(策略+模板)

背景

        最近需求要做一个活动需求,用户只要参与活动就可以获得奖励,奖励分为以下几种:

创角奖励: 用户在活动内的游戏创建角色即可中奖

等级奖励: 角色在游戏内级别达到某一个级别即可中奖

VIP级别奖励: 角色在游戏内VIP级别达到某一个级别即可中奖

排行榜奖励: 角色某一天充值榜一即可中奖

       

        如果按照传统的做法,就是每种中奖内部去进行判断逻辑,比如角色是没有啥条件的,等级是需要达到指定的等级,VIP需要达到指定的VIP级别等

        可能代码中就会存在大量的判断逻辑,而且前置逻辑都差不多,所以才会考虑用设计模式来进行处理

具体实现

设计图

代码逻辑实现

接口

public interface RebateActivityRecordHandler {

    RebateActivityRecordHandlerVo handler(Long userRoleId);

        RebateActivityPrizeSendTypeEnum getRebatePrizeSendType();
}

抽象类(核心)

@Slf4j
public abstract class AbstractRebateActivityRecordHandler implements RebateActivityRecordHandler{

    @Resource
    private IRebateActivityAttendUserService rebateActivityAttendUserService;
    @Resource
    @Lazy
    private IRebateActivityRoleRecordService rebateActivityRoleRecordService;
    @Resource
    private UserRoleService userRoleService;

    @Override
    public RebateActivityRecordHandlerVo handler(Long userRoleId) {

        RebateActivityRecordHandlerVo handlerVo = new RebateActivityRecordHandlerVo();

        UserRole userRole = userRoleService.getUserRole(userRoleId);
        if(userRole == null) {
            log.info("id为{}的角色不存在", userRoleId);
            return handlerVo;
        }

        //获取角色参与了哪个活动
        RebateActivityAttendUser rebateActivityAttendUser = rebateActivityAttendUserService.queryByRoleId(userRoleId);

        if(rebateActivityAttendUser == null) {
            log.info("roleId为 {} 没有参与任何一个活动,直接返回", userRoleId);
            return handlerVo;
        }
        
        //角色在某个活动内关联的数据
        List<RebateActivityRoleRecord> rebateActivityRoleRecordList = rebateActivityRoleRecordService.queryList(rebateActivityAttendUser.getRebateActivityId(), doGetRebatePrizeSendType(), userRoleId, userRole.getAppNumber());

        //调用模板方法进行真正的校验处理等
        return doHandler(userRole, rebateActivityAttendUser, rebateActivityRoleRecordList);
    }

    @Override
    public RebatePrizeSendTypeEnum getRebatePrizeSendType() {
        return doGetRebatePrizeSendType();
    }

    protected abstract RebatePrizeSendTypeEnum doGetRebatePrizeSendType();

    protected abstract RebateActivityRecordHandlerVo doHandler(UserRole userRole, RebateActivityAttendUser rebateActivityAttendUser, List<RebateActivityRoleRecord> rebateActivityRoleRecordList);
}

主要就是doHandler方法,下面看下其中一个实现类

创角奖励

@Service
@Slf4j
public class RebateActivityCreateRoleRecordHandler extends AbstractRebateActivityRecordHandler {

    @Override
    protected RebatePrizeSendTypeEnum doGetRebatePrizeSendType() {
        return RebatePrizeSendTypeEnum.CREATE_ROLE;
    }

    @Override
    protected RebateActivityRecordHandlerVo doHandler(UserRole userRole, RebateActivityAttendUser rebateActivityAttendUser, List<RebateActivityRoleRecord> rebateActivityRoleRecordList) {

        RebateActivityRecordHandlerVo recordHandlerVo = new RebateActivityRecordHandlerVo();

        if(rebateActivityRoleRecordList.size() >= RebateActivityGameConst.RECORD_COUNT) {
            log.info("活动创角奖励只能有 {} 个, 角色id为{}, 活动id为{}, 超出了直接返回", RebateActivityGameConst.RECORD_COUNT, userRole.getId(),null);
            return recordHandlerVo;
        }

        //创角奖励目前只有这个校验,如有其他的再加入
        RebateActivityRoleRecord rebateActivityRoleRecord = buildBaseRecord(userRole, rebateActivityAttendUser);

        recordHandlerVo.setSuccess(true);
        recordHandlerVo.setNeedSaveList(Collections.singletonList(rebateActivityRoleRecord));

        return recordHandlerVo;
    }
}

实现自己所需要的逻辑即可,其他的几种也是类似的,最后还有个上下文,如下

上下文

@Slf4j
@Component
public class RebateActivityRecordHandlerContext implements InitializingBean {


    @Resource
    private List<RebateActivityRecordHandler> recordHandlerList;

    private Map<RebatePrizeSendTypeEnum, RebateActivityRecordHandler> recordHandlerMap;



    @Override
    public void afterPropertiesSet() throws Exception {

        recordHandlerMap = new ConcurrentHashMap<>();

        RebatePrizeSendTypeEnum[] sendTypeEnums = RebatePrizeSendTypeEnum.values();
        for (RebatePrizeSendTypeEnum sendTypeEnum : sendTypeEnums) {
            recordHandlerMap.put(sendTypeEnum, query(sendTypeEnum));
        }
    }

    private RebateActivityRecordHandler query(RebatePrizeSendTypeEnum prizeSendType) {
        for (RebateActivityRecordHandler rebateActivityRecordHandler : recordHandlerList) {
            if(Objects.equals(rebateActivityRecordHandler.getRebatePrizeSendType(), prizeSendType)) {
                return rebateActivityRecordHandler;
            }
        }
        throw new IllegalArgumentException("类型为 " + prizeSendType.getDesc() + " 没有处理类,请参考RebatePrizeSendTypeEnum");
    }

    private RebateActivityRecordHandler assertHandler(RebatePrizeSendTypeEnum prizeSendType) {
        RebateActivityRecordHandler recordHandler = recordHandlerMap.get(prizeSendType);
        if(recordHandler == null) {
            throw new AppException(ErrorCode.SYS_ERROR.code(), "找不到活动中奖类型的处理类,类型为" + prizeSendType.getDesc());
        }
        return recordHandler;
    }

    public RebateActivityRecordHandlerVo handleRebateActivityRecord(Long userRoleId, RebatePrizeSendTypeEnum prizeSendType) {
        return assertHandler(prizeSendType).handler(userRoleId);
    }
}

注:这个类其实也可以不要,只是习惯性会用这么个东西,而且利用了spring的初始化方法来判断是否所有奖励类型都有对应的处理类,还是有意义的

备注:这个设计跟我另一篇文章很类似,如下,状态模式的

活动功能->状态模式的使用_活动状态模式代码-CSDN博客

总结

        主要采用的就是策略模式+模板模式,意义分别体现在

模板模式:  把基本的校验信息,比如活动是不是存在,角色是不是存在,角色是不是有参与某个活动逻辑都放在AbstractRebateActivityRecordHandler类中进行处理,这样的好处是其他几种中奖时前置判断条件就被统一了,假设以后要调整可以统一调整即可

策略模式: 把每种中奖逻辑单独用类封装起来,自己去实现要过滤的逻辑相互不影响,也更容易找到对应的地方进行修改,后续如果有其他的中奖记录根据这种类型添加实现类即可

整体来说,好处就是逻辑解耦了,但是代码量多了,而且没有研究过设计模式的可能一时半会看不太懂是什么意思

模式对比

       

状态模式: 也就是当前的模式, 每种策略都必须实现接口的方法,因为只是实现不同

状态模式: 参考我上面的文章,它跟策略模式很像,但是还是有区别的,每种状态不一定会实现所有方法,比如结束状态的实现类就是个空实现,因为他不能切换为任何一种状态

这是两者间一个很明显的对比,而且状态模式肯定是有很多方法不需要实现的,状态切换是有一定的规则的

        

相关推荐

  1. 设计模式——策略模式

    2023-12-22 13:34:04       30 阅读
  2. 设计模式-策略模式

    2023-12-22 13:34:04       38 阅读
  3. 设计模式——策略模式

    2023-12-22 13:34:04       29 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-22 13:34:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-22 13:34:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-22 13:34:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-22 13:34:04       20 阅读

热门阅读

  1. C/C++ 基础函数

    2023-12-22 13:34:04       38 阅读
  2. GO设计模式——24、策略模式(行为型)

    2023-12-22 13:34:04       41 阅读
  3. python 条件控制语句(基础)学习笔记

    2023-12-22 13:34:04       36 阅读
  4. js获取年月日的格式

    2023-12-22 13:34:04       30 阅读
  5. Flink系列之:Elasticsearch SQL 连接器

    2023-12-22 13:34:04       38 阅读
  6. git如何修改提交代码时的名字和邮箱?

    2023-12-22 13:34:04       39 阅读
  7. 华为常用命令大全

    2023-12-22 13:34:04       32 阅读