工厂+策略+模板模式实战

导读:本篇文章讲解 工厂+策略+模板模式实战,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、前言

最近项目中连续遇到两次业务都需要用到工厂+策略模式来实现。具体业务如下,可以参考参考和自己的业务是不是相似。

1.系统预警功能

这个功能刚开始就包含三十多个指标,后续可能还会增加。为了以后增加指标的时候代码的扩展性强,而不是在一个方法里面if-else,就使用到这种工厂+策略的方式来实现。

2.三重福利功能

这个功能就是判断当前用户是否完成了具体任务,然后领取优惠券。虽然当时接到任务的时候需求就只有三个福利类型,但是为了预防以后增加福利的时候,能快速实现,也就采用了工厂+策略的方式来实现。

二、实现思路

下面通过三重福利来讲一讲具体的思路。

1.业务逻辑

首先需要明白这个功能的实现步骤。三重福利的具体实现思路如下:
在这里插入图片描述
我们会发现其实无论你福利类型怎么变化,有多少个,核心的逻辑就是这几点,这样的话,我们就可以使用模板的方式来实现。

2.工厂搭建

工厂主要是通过福利类型来生产具体的Strategy Handler,然后具体的Strategy Handler根据自己的逻辑去处理具体的业务。
(1)工厂接口:

/**
 * @description: 福利工厂
 * @author: Felix.Du
 * @date: 2021/10/13 4:15 下午
 */
public interface BenefitsFactory {

    /**
     * 根据福利类型创建福利handler
     *
     * @param benefitsType
     * @return 福利handler
     */
    BenefitsHandler createBenefitsHandler(BenefitsTypeEnum benefitsType);
}

(2)工厂实现类:

/**
 * @description:
 * @author: Felix.Du
 * @date: 2021/10/13 4:28 下午
 */
@Component
@Slf4j
public class BenefitsFactoryImpl implements BenefitsFactory, ApplicationContextAware {

    private final Map<Integer, Object> typeToHandlerMap = new HashMap<>();

    private ApplicationContext applicationContext;

    @Override
    public BenefitsHandler createBenefitsHandler(BenefitsTypeEnum benefitsType) {
        try {
            return getHandler(benefitsType);
        } catch (Exception e) {
            log.error("福利类型为{}的handler获取失败,请检查", benefitsType, e);
            throw new RuntimeException();
        }
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private BenefitsHandler getHandler(BenefitsTypeEnum benefitsType) {

        BenefitsTypeEnum[] handlers = BenefitsTypeEnum.values();
        if (CollUtil.isEmpty(typeToHandlerMap)
                || !Integer.valueOf(typeToHandlerMap.size()).equals(handlers.length)) {

            initTypeToStrategyMap(handlers);
        }

        Object handler = typeToHandlerMap.get(benefitsType.getCode());
        if (Objects.isNull(handler)) {

            throw new RuntimeException("获取到的福利类为null");
        }
        return (BenefitsHandler) handler;
    }

    private void initTypeToStrategyMap(BenefitsTypeEnum[] handlers) {

        typeToHandlerMap.clear();
        for (BenefitsTypeEnum handler : handlers) {

            try {
                Class<?> aClass = Class.forName(handler.getClazzName());
                Object bean = applicationContext.getBean(aClass);
                typeToHandlerMap.put(handler.getCode(), bean);
            } catch (ClassNotFoundException e) {
                log.error("福利Handler{}无法加载", handler.getCode(), e);
            }
        }
    }
}

(3)工厂涉及到的枚举类BenefitsTypeEnum:

package net.spo.enumerate;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
 * @description: 福利类型枚举
 * @author: Felix.Du
 * @date: 2021/10/13 3:51 下午
 */
@AllArgsConstructor
public enum BenefitsTypeEnum {

    /**
     * 注册成功
     */
    REGISTRATION_SUCCESS(1, "net.spo.handler.impl.RegistrationBenefitsHandler"),

    /**
     * 邀请好友
     */
    INVITE_FRIENDS(2,"net.spo.handler.impl.InviteFriendsBenefitsHandler"),

    /**
     * 添加官方客服
     */
    ADD_CUSTOMER_SERVICE_STAFF(3, "net.spo.handler.impl.BindCusServiceBenefitsHandler");

    @Getter
    private Integer code;

    /**
     * 全类名
     */
    @Getter
    private final String clazzName;

    public static BenefitsTypeEnum getBenefitsTypeByCode(Integer code){

        return Arrays
                .stream(BenefitsTypeEnum.values())
                .filter(benefitsTypeEnum -> code.equals(benefitsTypeEnum.getCode()))
                .findFirst()
                .get();

    }
}

3.模板搭建

我们通过福利类型传入工厂,能够拿到具体的策略处理器实体类,接下来就可以着手写模板方法了。
(1)福利处理器 BenefitsHandler 接口:

/**
 * @description: 福利处理器
 * @author: Felix.Du
 * @date: 2021/10/13 4:40 下午
 */
public interface BenefitsHandler {

    /**
     * 整个领奖流程,子类不需要实现
     *
     * @param userId 用户id
     * @param benefitsType 福利类型
     * @return 算力券/算力模板详情,用于领取后弹框
     */
    List<HashrateCouponAndTemplateVO> entireBenefitsProcess(Long userId, BenefitsTypeEnum benefitsType);

    /**
     * 判断当前用户是否满足福利所需条件(具体地判断逻辑方法需要子类实现)
     *
     * @param userId 用户id
     * @return true:完成; false:未完成
     */
    boolean checkWhetherMissionAccomplished(Long userId);

    /**
     * 领取福利,子类不需要实现
     *
     * @param userId 用户id
     * @param benefitsType 福利类型
     * @return 算力券/算力模板详情,用于领取后弹框
     */
    List<HashrateCouponAndTemplateVO> receiveBenefits(Long userId, BenefitsTypeEnum benefitsType);

    /**
     * 查询活动完成进度
     * key->进度编号, value->进度情况
     * @param userId  用户id
     * @return 活动完成进度,key->进度编号, value->进度情况
     */
    Map<String, String> getAccomplishedProgress(Long userId);

}

(2)抽象模板 AbstractBenefitsHandler :

/**
 * @description: 福利抽象类
 * @author: Felix.Du
 * @date: 2021/10/13 4:54 下午
 */
public abstract class AbstractBenefitsHandler implements BenefitsHandler {

    @Resource
    public MineUserBenefitsMarkMapper mineUserBenefitsMarkMapper;

    @Resource
    public RocketMQTemplate rocketMqTemplate;

    @Resource
    public MineHashrateCouponCustomerService mineHashrateCouponCustomerService;

    @Resource
    public MineHashrateTemplateCustomerService mineHashrateTemplateCustomerService;

    @Resource
    public CustomerClient customerClient;

    @Resource
    public MineHashrateTemplateService mineHashrateTemplateService;

    @Resource
    public MineHashrateTemplateCustomerMapper mineHashrateTemplateCustomerMapper;

    @Resource
    public MineHashrateCouponCustomerMapper mineHashrateCouponCustomerMapper;

    @Override
    public List<HashrateCouponAndTemplateVO> entireBenefitsProcess(Long userId, BenefitsTypeEnum benefitsType) {

        //查询是否已经领取该福利
        boolean whetherReceive = getBenefitsMark(userId, benefitsType);

        //已领取,返回错误提示
        AssertsUtil.isTrue(whetherReceive, ResultCode.BusinessCode.REPEAT_RECEIVE);

        //判断是否完成领取任务
        boolean whetherAccomplish = checkWhetherMissionAccomplished(userId);
        AssertsUtil.isFalse(whetherAccomplish, ResultCode.BusinessCode.UNFINISHED_TASK);

        //未领取且完成任务,直接领奖
        List<HashrateCouponAndTemplateVO> hashrateCouponAndTemplateVos = receiveBenefits(userId, benefitsType);

        AssertsUtil.isTrue(CollectionUtils.isEmpty(hashrateCouponAndTemplateVos), ResultCode.BusinessCode.RECEIVE_FAILED);

        //修改用户福利领取标识表
        updateUserMark(userId, benefitsType);

        return hashrateCouponAndTemplateVos;

    }


	/**
     * 领取福利,子类不需要实现
     *
     * @param userId 用户id
     * @param benefitsType 福利类型
     * @return 算力券/算力模板详情,用于领取后弹框
     */
    @Override
    public List<HashrateCouponAndTemplateVO> receiveBenefits(Long userId, BenefitsTypeEnum benefitsType) {

        CustomerWeChatInfoDTO cusInfo = customerClient.getWeChatInfo(userId).getData();

        Enumerate.DistributionsStatus distributionsStatus = getDistributionsStatusByBenefitsType(benefitsType);

        //获取可分配的算力模板和券
        var list = mineHashrateTemplateService.lambdaQuery()
                .eq(MineHashrateTemplate::getEnableStatus, Enumerate.IsEnable.ENABLE.getValue())
                .eq(MineHashrateTemplate::getDistributionsStatus, distributionsStatus.getCode())
                .list();
        //待分配模板
        var templateList = list
                .stream()
                .filter(item -> Objects.equals(item.getType(), HashrateTemplateEnum.TEMPLATE.getCode()))
                .map(item -> {
                    var templateCouponDTO = new TopTemplateCouponDTO();
                    templateCouponDTO.setId(item.getId());
                    templateCouponDTO.setReason(distributionsStatus.getReason());
                    var dto = new TopRewardDTO();
                    dto.setUserId(cusInfo.getId());
                    dto.setRegisterAccount(cusInfo.getRegisterAccount());
                    templateCouponDTO.setList(List.of(dto));
                    return templateCouponDTO;
                }).collect(Collectors.toList());
        //待分配劵
        var couponList = list
                .stream()
                .filter(item -> Objects.equals(item.getType(), HashrateTemplateEnum.COUPON.getCode()))
                .map(item -> {
                    var templateCouponDTO = new TopTemplateCouponDTO();
                    templateCouponDTO.setId(item.getId());
                    templateCouponDTO.setReason(distributionsStatus.getReason());
                    var dto = new TopRewardDTO();
                    dto.setUserId(cusInfo.getId());
                    dto.setRegisterAccount(cusInfo.getRegisterAccount());
                    templateCouponDTO.setList(List.of(dto));
                    return templateCouponDTO;
                }).collect(Collectors.toList());
        //分配模板
        var templateCustomerIdList = mineHashrateTemplateCustomerService.allocationHashrateTemplateByTop(templateList, new TopDurationDTO());
        List<HashrateCouponAndTemplateVO> oneTemplateCount1 = new ArrayList<>();
        List<HashrateCouponAndTemplateVO> oneTemplateCount2 = new ArrayList<>();
        AtomicLong atomicLong = new AtomicLong();
        if (!CollectionUtils.isEmpty(templateCustomerIdList)) {
            var queryWrapper = new QueryWrapper<>();
            queryWrapper.in("a.id", templateCustomerIdList);
            oneTemplateCount1 = mineHashrateTemplateCustomerMapper.selectPageHashrateTemplateCustomer(null, queryWrapper)
                    .stream()
                    .map(item -> {
                        //弹出算力券详情
                        HashrateCouponAndTemplateVO hashrateCouponAndTemplateVO = new HashrateCouponAndTemplateVO();
                        hashrateCouponAndTemplateVO.setName(item.getName());
                        hashrateCouponAndTemplateVO.setUnit(item.getUnit());
                        hashrateCouponAndTemplateVO.setHashrateType(item.getHashrateType());
                        hashrateCouponAndTemplateVO.setEachAmount(item.getEachAmount());
                        hashrateCouponAndTemplateVO.setValidity(item.getValidity());
                        hashrateCouponAndTemplateVO.setHashrateTemplateId(item.getId());
                        hashrateCouponAndTemplateVO.setId(atomicLong.addAndGet(1));
                        hashrateCouponAndTemplateVO.setType(HashrateTemplateEnum.TEMPLATE.getCode());
                        return hashrateCouponAndTemplateVO;
                    }).collect(Collectors.toList());

        }
        //分配劵
        var couponIdList = mineHashrateCouponCustomerService.allocationHashrateCouponByTop(couponList, new TopDurationDTO());
        if (!CollectionUtils.isEmpty(couponIdList)) {
            var queryWrapper = new QueryWrapper<>();
            queryWrapper.in("a.id", couponIdList);
            oneTemplateCount2 = mineHashrateCouponCustomerMapper.selectPageHashrateCouponCustomer(null, queryWrapper)
                    .stream()
                    .map(item -> {
                        //弹出算力券详情
                        HashrateCouponAndTemplateVO hashrateCouponAndTemplateVO = new HashrateCouponAndTemplateVO();
                        hashrateCouponAndTemplateVO.setName(item.getName());
                        hashrateCouponAndTemplateVO.setUnit(item.getUnit());
                        hashrateCouponAndTemplateVO.setHashrateType(item.getHashrateType());
                        hashrateCouponAndTemplateVO.setEachAmount(item.getEachAmount());
                        hashrateCouponAndTemplateVO.setValidity(item.getValidity());
                        hashrateCouponAndTemplateVO.setHashrateTemplateId(item.getId());
                        hashrateCouponAndTemplateVO.setId(atomicLong.addAndGet(1));
                        hashrateCouponAndTemplateVO.setType(HashrateTemplateEnum.COUPON.getCode());
                        return hashrateCouponAndTemplateVO;
                    }).collect(Collectors.toList());
        }
        oneTemplateCount1.addAll(oneTemplateCount2);
        return oneTemplateCount1;
    }

    /**
     * 根据用户id和福利类型查询是否已经领取该福利
     *
     * @param userId       用户id
     * @param benefitsType 福利类型
     * @return true:已领取; false:未领取
     */
    public boolean getBenefitsMark(Long userId, BenefitsTypeEnum benefitsType) {
        MineUserBenefitsMark mineUserBenefitsMark = mineUserBenefitsMarkMapper.selectOne(
                Wrappers
                        .lambdaQuery(MineUserBenefitsMark.class)
                        .eq(MineUserBenefitsMark::getUserId, userId)
                        .eq(MineUserBenefitsMark::getBenefitsTypeId, benefitsType.getCode()));

        //如果查询为空,说明还没领取,无创建记录
        if (Objects.isNull(mineUserBenefitsMark)) {
            return false;
        }

        return mineUserBenefitsMark.getMark() == UserMarkEnum.RECEIVED.getCode();
    }

    /**
     * 根据福利类型获取算力模板自动分配类型
     *
     * @param benefitsType 福利类型
     * @return 算力模板自动分配类型
     */
    public Enumerate.DistributionsStatus getDistributionsStatusByBenefitsType(BenefitsTypeEnum benefitsType) {

        Enumerate.DistributionsStatus distributionsStatus;
        switch (benefitsType) {
            case REGISTRATION_SUCCESS:
                distributionsStatus = Enumerate.DistributionsStatus.C_TRIGGER_REGISTER;
                break;
            case INVITE_FRIENDS:
                distributionsStatus = Enumerate.DistributionsStatus.C_TRIGGER_INVITE_FRIENDS;
                break;
            default:
                distributionsStatus = Enumerate.DistributionsStatus.C_TRIGGER_ADD_CUSTOMER_SERVICE;
                break;
        }
        return distributionsStatus;

    }

    /**
     * 领取成功后,修改用户福利领取标识
     *
     * @param benefitsType 福利类型
     */
    public void updateUserMark(Long userId, BenefitsTypeEnum benefitsType) {

        MineUserBenefitsMark mineUserBenefitsMark = mineUserBenefitsMarkMapper.selectOne(
                Wrappers
                        .lambdaQuery(MineUserBenefitsMark.class)
                        .eq(MineUserBenefitsMark::getUserId, userId)
                        .eq(MineUserBenefitsMark::getBenefitsTypeId, benefitsType.getCode())
        );

        //为空说明未领取,直接新增记录
        if(Objects.isNull(mineUserBenefitsMark)){

            MineUserBenefitsMark userBenefitsMark = new MineUserBenefitsMark();
            userBenefitsMark.setUserId(userId);
            userBenefitsMark.setBenefitsTypeId(Long.valueOf(benefitsType.getCode()));
            userBenefitsMark.setMark(UserMarkEnum.RECEIVED.getCode());

            mineUserBenefitsMarkMapper.insert(userBenefitsMark);
        }else {
            //不为空则修改状态
            mineUserBenefitsMark.setMark(UserMarkEnum.RECEIVED.getCode());
            mineUserBenefitsMarkMapper.updateById(mineUserBenefitsMark);
        }

    }
}

这里需要注意:AbstractBenefitsHandler福利模板需要去实现BenefitsHandler接口,抽象类可以选择实现接口的部分方法,这里就需要抽象类去实现抽象类的子类不实现的方法了。举个例子,比如说BenefitsHandler中有一个 判断当前用户是否满足福利所需条件 的方法checkWhetherMissionAccomplished(Long userId),很明显每福利的判断逻辑都不一样,这个需要子类自己去实现,写自己的逻辑,而模板父类不能关心子类的逻辑是如何的,这个时候模板类就不需要去实现这个方法了,而领取流程entireBenefitsProcess(Long userId, BenefitsTypeEnum benefitsType) 这个方法所有福利类型都是一样的逻辑,子类不会去关心,这个时候父类就需要去实现该方法,子类直接继承父类就行了。
(3)InviteFriendsBenefitsHandler 邀请好友福利处理器

@Component
@RequiredArgsConstructor
@Slf4j
public class InviteFriendsBenefitsHandler extends AbstractBenefitsHandler {

    private final CustomerActivityClient customerActivityClient;

    /**
     * 邀请好友个数要求
     */
    private static final Integer INVITE_LIMIT = 3;
    /**
     * 邀请好友实名认证个数限制
     */
    private static final Integer INVITE_KYC_LIMIT = 2;

    /**
     * 邀请好友需要:1 邀请3人;2 邀请好友完成实名认证3人;
     *
     * @param userId 用户id
     * @return 是否完成任务
     */
    @Override
    public boolean checkWhetherMissionAccomplished(Long userId) {

        //获取已邀请好友信息
        List<CustomerAccountDTO> invitedUsers = customerActivityClient.getInvitedUser(userId).getData();
        if (invitedUsers.size() < INVITE_LIMIT) {

            return Boolean.FALSE;
        }
        List<CustomerAccountDTO> kycUsers = invitedUsers
                .stream()
                .filter(u -> NumberUtil.equals(u.getKycStatus(), Enumerate.UserKycStatus.VERIFIED.getStatus()))
                .collect(Collectors.toList());
        if (kycUsers.size() < INVITE_KYC_LIMIT) {

            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 邀请好友需要:1 邀请3人;2 邀请好友完成实名认证3人;
     * @param userId 用户id
     * @return 活动完成进度,key->进度编号, value->进度情况
     */
    @Override
    public Map<String, String> getAccomplishedProgress(Long userId) {

        UUIDContext.setUUID(UUID.randomUUID());
        List<CustomerAccountDTO> invitedUsers = customerActivityClient.getInvitedUser(userId).getData();
        int invitedAmount = 0;
        int invitedKycAmount = 0;
        if (!CollUtil.isEmpty(invitedUsers)){

            invitedAmount = invitedUsers.size();
            List<CustomerAccountDTO> kycUsers = invitedUsers
                    .stream()
                    .filter(u -> NumberUtil.equals(u.getKycStatus(), Enumerate.UserKycStatus.VERIFIED.getStatus()))
                    .collect(Collectors.toList());
            invitedKycAmount = kycUsers.size();
        }

        String conditionInvite = invitedAmount + "/" + INVITE_LIMIT;
        String conditionKyc = invitedKycAmount + "/" + INVITE_KYC_LIMIT;

        Map<String, String> conditionMap = new HashMap<>(4);
        conditionMap.put(BenefitsConstants.CONDITION_1, conditionInvite);
        conditionMap.put(BenefitsConstants.CONDITION_2, conditionKyc);

        return Map.of(BenefitsConstants.CONDITION_1, invitedAmount + "/" + INVITE_LIMIT, 
                BenefitsConstants.CONDITION_2, invitedKycAmount + "/" + INVITE_KYC_LIMIT);
}

我们可以看到子类只需要去实现判断当前用户是否完成指定任务checkWhetherMissionAccomplished方法,以及获取完成进度getAccomplishedProgress方法。其他方法子类不需要关心,由父类统一处理。这样我们在遇到在增加一个福利类型的时候就很简单了,只需要增加一个具体的福利类型Strategy Handler,然后去继承模板父类AbstractBenefitsHandler,同时再去实现上诉两个方法即可。其他的什么是否已经领取过,然后再去判断是否完成任务,最后再去领取奖励,这些逻辑完全不用关心。
还有一点需要注意,如果业务上增加了一个流程不同的福利,你也可以通过继承模板父类,然后覆写他的方法即可实现。

新公司短短工作两个月,就遇到了两次需要使用这种模式的业务,所以决定记录一下,方便自己温故而知新。

看没看懂都点个赞呗。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/67265.html

(0)
小半的头像小半

相关推荐

半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!