【设计模式活用】之switch case重构案例精讲

目前系统应用中,有一个业务功能模块,是提供一个页面查询结果接口,通过参数控制以调用查询不同接口数据集,并对查询结果集做相应处理,封装并返回调用方。但是,该方法体使用了switch case控制不同业务场景,造成方法体行数较长,而每次新增业务又需要变动该方法,存在冗余的代码。鉴于此,通过分析业务逻辑,并进行抽象封装。

1、案例再现

1.1、原始的switch case 业务代码逻辑
public Result<PageVO> queryBindRules(PageForm<PdRuleProductForm> form) {
        LOGGER.info("产品管理 组件配置 - queryBindRules 参数:{}",JSON.toJSON(form));
        ValidateUtils.notNull(form, HeilCode.E_400, "productId不能为空");
        ValidateUtils.notNull(form.getForm().getProductId(),HeilCode.E_400,"productId不能为空");
        ValidateUtils.notNull(form.getForm().getClassify(),HeilCode.E_400,"classify不能为空");
        BuzTypeEnum buzTypeEnum = BuzTypeEnum.getByIndex(form.getForm().getClassify());
        ValidateUtils.notNull(buzTypeEnum,HeilCode.E_400,"不支持当前业务类型");
        //1、这里通过引入开关控制调用新重构代码
        boolean switchEnable = configParamsFacade.getValueByParamKey("feeRuleProductLinkEnable",Boolean.class);
        if(switchEnable){
            QueryLinkContext context = QueryLinkContext.builder()
                    .yanbaoRuleFacade(yanbaoRuleFacade).commonComponent(commonComponent)
                    .pdRuleProductService(pdRuleProductService).serFinRuleFacade(serFinRuleFacade)
                    .form(form).build();
            QueryLinkHandlerFactory.create(buzTypeEnum).execute(context);
            return Result.suc(PageVO.newInstance(form.getDraw(),context.getCount(),context.getVoList()));
        }
        // 2、下面是最原始的业务代码
        switch (buzTypeEnum) {
            case SER_FIN_RULE:
                List<SerFinRule> serFinRules = pdRuleProductService.querySerFinRules(form);
                if (CollectionsTools.isEmpty(serFinRules)) {
                    return Result.failInEmptyRecord(null);
                }
                List<SerFinRuleVo> serFinRuleVos = new SerFinRuleVoConvertor().convertList(serFinRules);
                serFinRuleFacade.formatSerFinRule(serFinRules, serFinRuleVos);
                int serFinRulesCount = pdRuleProductService.querySerFinRulesCount(form);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.SER_FIN_RULE.getIndex(),serFinRuleVos),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(serFinRuleVos));
                return Result.suc(new PageVO<>(form.getDraw(),serFinRulesCount,serFinRuleVos));

            case RATE_RULE:
                List<RateRule> rateRules = pdRuleProductService.queryRateRules(form);
                List<RateRuleVo> rateRuleVos = new RateRuleVoConvertor().convertList(rateRules);

                int count = pdRuleProductService.queryRateRulesCount(form);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.RATE_RULE.getIndex(),rateRuleVos),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(rateRuleVos));
                return Result.suc(new PageVO<>(form.getDraw(),count,rateRuleVos));
            case GPS_RULE:
                List<GpsRule> gpsRules = pdRuleProductService.queryGpsRules(form);
                List<GpsRuleVo> gpsRuleVos = new GpsRuleVoConvertor().convertList(gpsRules);
                gpsRuleFacade.formatGpsRuleVo(gpsRuleVos);
                int gpsCount = pdRuleProductService.queryGpsRulesCount(form);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.GPS_RULE.getIndex(),gpsRuleVos),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(gpsRuleVos));
                return Result.suc(new PageVO<>(form.getDraw(), gpsCount, gpsRuleVos));
            case INSURANSE_SECOND_YEAR:
                form.getForm().setYanBaoClassify(2);
                List<YanbaoRule> yanbaoRules = pdRuleProductService.queryYanbaoRules(form);
                List<YanbaoRuleVo> yanbaoRuleVos = new YanbaoRuleVoConvertor().convertList(yanbaoRules);
                int yanbaoCount = pdRuleProductService.queryYanbaoRulesCount(form);
                yanbaoRuleFacade.formatYanbaoRules(yanbaoRules,yanbaoRuleVos);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.INSURANSE_SECOND_YEAR.getIndex(),yanbaoRuleVos),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(yanbaoRuleVos));
                return Result.suc(new PageVO<>(form.getDraw(), yanbaoCount, yanbaoRuleVos));
            case INSURANSE_THIRD_YEAR:
                form.getForm().setYanBaoClassify(3);
                List<YanbaoRule> yanbaoRules3 = pdRuleProductService.queryYanbaoRules(form);
                List<YanbaoRuleVo> yanbaoRuleVos3 = new YanbaoRuleVoConvertor().convertList(yanbaoRules3);
                int yanbaoRuleCount = pdRuleProductService.queryYanbaoRulesCount(form);
                yanbaoRuleFacade.formatYanbaoRules(yanbaoRules3, yanbaoRuleVos3);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.INSURANSE_THIRD_YEAR.getIndex(),yanbaoRuleVos3),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(yanbaoRuleVos3));
                return Result.suc(new PageVO<>(form.getDraw(), yanbaoRuleCount, yanbaoRuleVos3));
            case ACCOUNT_RULE:
                List<AccountRule> accountRules = pdRuleProductService.queryAccountRules(form);
                List<AccountRuleVo> accountRuleVos = new AccountRuleVoConvertor().convertList(accountRules);
                int accountCount = pdRuleProductService.queryGpsRulesCount(form);
                commonComponent.bindTags(TwoTuple.newInstance(BuzTypeEnum.ACCOUNT_RULE.getIndex(),accountRuleVos),
                        rule -> Integer.valueOf(rule.getRuleSeq()),(rule, tags) -> rule.setTags(tags));
                LOGGER.info("规则关联产品-queryBindRules-业务类型:{},结果:{}", buzTypeEnum.getName(), JSON.toJSON(accountRuleVos));
                return Result.suc(new PageVO<>(form.getDraw(), accountCount, accountRuleVos));
        }
        return Result.suc(new PageVO<>(form.getDraw(), 0, null));
    }

从上述代码我们通过分析得出一个结论,每一种case场景,都是通过分页查询结果集以及分页总条数,然后把查询的实体对象转换成VO对象,并对VO对象通过调用bindTags方法,迭代每一个集合元素设置对应对象元素的标签属性。

1.2、槽点分析

但是上述代码我们同样可以看出有以下几个槽点:

  • 1、每个方法中都存在类似的行为操作,查数据==>转换数据==>封装数据==>绑定标签==>装载返回Result对象。
  • 2、每一次增加一种case场景,都要对现有代码修改,不符合“开闭原则”。
  • 3、每一种case场景,都有冗余的代码,不符合“DRY原则"(Don’t Repeat Yourself)。
  • 4、方法代码行数过长,不便于后期迭代阅读,方法体臃肿。
1.3、函数式编程

方法中调用的一个对查询结果集设置标签的一个独立方法

/**
     * 对相关查询的规则VO绑定标签
     * 适用业务场景:平台费规则、利率规则、GPS规则 配置标签,查询列表返回相应标签
     * @param tuple 数据传输通道容器
     * @param function 回调函数,返回集合元素对象,让调用方返回对应的主键id
     * @param consumer 接口,返回获取的tags
     */
    public <T,R> void bindTags(TwoTuple<Integer,List<T>> tuple, Function<T,R> function, BiConsumer<T,List<String>> consumer){
        try {
            PdTagForm form = new PdTagForm();
            form.setBuzType(tuple.getA());
            List<PdTag> tagList = this.pdTagService.queryList(form);
            if(CollectionsTools.isNotEmpty(tagList)){
                Map<Integer,String> tagMap = tagList.stream().collect(Collectors.toMap(PdTag::getSourceId,PdTag::getTags));
                tuple.getB().forEach(rule -> {
                    String tags = tagMap.get(function.apply(rule));
                    if(StringTools.isNotEmpty(tags)){
                        consumer.accept(rule,Arrays.asList(tags.split(",")));
                    }
                });
            }
        } catch (Exception e) {
            logger.error("{}设置标签异常,tuple={}",JSON.toJSON(tuple),e);
        }
    }

上述代码,我们看到是对switch case中绑定标签的处理做了一个抽象封装。这里运用了JDK8两个重要的接口,Function<T,R>,BiConsumer<T,U>,通过提供函数式编程特性,摒弃传统的回调函数编码风格。

  • Function<T,R>:这个接口我们可以通过分析源码得出,给出一个T对象,返回一个R结果,通过调用R apply(T t);方法。
  • BiConsumer<T,U>:这个接口也是一个回调接口,通过void accept(T t,U u)方法,把处理的结果提供给调用方消费。

2、弑魂大法

我们从上述章节【槽点分析】中,经过思路缜密分析,对于槽点1我们通过模板方法封装行为,对于槽点2我们可以把每一种case中作为抽象类的子类,对于那么多case,我们可以通过工厂模式封装。

2.1、UML类图

UML类图

从上述类图中我们看出,QueryLink是一个数据查询接口,AbstractQueryLinkHandler是一个抽象类,继承QueryLink接口,封装抽象行为,SerFinQueryLinkHandler等是对应switch case的场景,也就是要继承抽象类并实现相应的查询接口,QueryLinkContext是一个上线文对象,透传各个类交互传输,QueryLinkHandlerFactory负责生产Handler对象,交予调用发调用。

2.2、QueryLink

负责分页查询结果集及查询总条数,并规约查询结果集对象是BaseEntity子类,其中查询参数Form对象要继承PdRuleProductForm类。

/**
 * @description: 查询费用规则关联产品接口
 * @Date : 2018/11/20 下午2:07
 * @Author : 石冬冬-Seig Heil
 */
public interface QueryLink<E extends BaseEntity,F extends PdRuleProductForm> {
    /**
     * 查询已关联集合
     * @param form
     * @return
     */
    List<E> queryLink(PageForm<F> form);
    /**
     * 查询已关联条数
     * @param form
     * @return
     */
    int queryLinkCount(PageForm<F> form);
}

2.3、QueryLinkContext

通过引入lombok注解,实现getter/setter方法,以及支持builder建造者模式构建对象。该对象包装相关抽象类子类依赖的接口,通过上下文对象初始化。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @description: 查询费用规则关联上下文对象
 * @Date : 2018/11/20 下午2:16
 * @Author : 石冬冬-Seig Heil
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class QueryLinkContext {
    protected PdRuleProductService pdRuleProductService;
    protected CommonComponent commonComponent;
    protected YanbaoRuleFacade yanbaoRuleFacade;
    protected SerFinRuleFacade serFinRuleFacade;
    protected BuzType buzType;
    protected PageForm<PdRuleProductForm> form;
    protected int count;
    protected List<BaseVo> voList;
}
2.4、AbstractQueryLinkHandler

该抽象类提供几个方法,prepare、query、call、convertVo、bindTags、execute方法。其中,execute声明public作用域,是提供给调用方调用的。convertVo是一个抽象方法,是负责把entity转换vo,由抽象类的子类去负责实现。

/**
 * @description: 抽象查询关联产品处理器
 * @Date : 2018/11/20 下午2:14
 * @Author : 石冬冬-Seig Heil
 */
public abstract class AbstractQueryLinkHandler<E extends BaseRule> implements QueryLink {
    /**
     * 处理器名称
     */
    protected String handlerName;
    /**
     * 依赖查询service接口
     */
    protected PdRuleProductService pdRuleProductService;
    /**
     * 依赖通用设置接口
     */
    protected CommonComponent commonComponent;
    /**
     * 查询数量
     */
    protected int count;
    /**
     * 查询实体对象
     */
    protected List<E> entities;
    /**
     * VO实体集合对象
     */
    protected List<BaseVo> voList;
    /**
     * 业务类型
     */
    protected TagConstant.BuzTypeEnum buzTypeEnum;
    /**
     * 查询条件
     */
    protected PageForm<PdRuleProductForm> form;

    /**
     * 默认构造函数
     */
    public AbstractQueryLinkHandler() {
    }

    /**
     * 构造函数
     * @param handlerName 处理器名称
     * @param buzTypeEnum 业务类型 已关联或未关联
     */
    public AbstractQueryLinkHandler(String handlerName, TagConstant.BuzTypeEnum buzTypeEnum) {
        this.handlerName = handlerName;
        this.buzTypeEnum = buzTypeEnum;
    }

    /**
     * 初始化相关成员变量
     * @param context
     */
    protected void prepare(QueryLinkContext context){
        this.pdRuleProductService = context.getPdRuleProductService();
        this.commonComponent = context.getCommonComponent();
        this.form = context.getForm();
    }

    /**
     * 查询关联数据
     */
    protected void query(){
        entities = queryLink(form);
        count = queryLinkCount(form);
    }

    /**
     * 相关设置调用
     */
    protected void call(QueryLinkContext context){
        context.setCount(count);
        voList = convertVo();
        context.setVoList(voList);
    }

    /**
     * entities 转换 vo
     * @return
     */
    abstract List<BaseVo> convertVo();

    /**
     * 对查询的VO集合绑定标签字段
     */
    protected void bindTags(){
        if(null == voList || voList.isEmpty()){
            return;
        }
        commonComponent.bindTags(TwoTuple.newInstance(buzTypeEnum.getIndex(),voList),
                (rule) -> Integer.valueOf(((BaseRuleVo)rule).getRuleSeq()),(rule, tags) -> ((BaseRuleVo)rule).setTags(tags));
    }

    /**
     * 外部调用公共方法
     * @param context
     */
    public final void execute(QueryLinkContext context){
        prepare(context);
        query();
        call(context);
        bindTags();
    }
}
2.5、SerFinQueryLinkHandler

我们从重构前的代码中看出,对于SER_FIN,其中有特殊操作,所以来说,该子类重写了抽象类的prepare、call方法。

/**
 * @description: 平台费规则查询产品关联处理器
 * @Date : 2018/11/20 下午5:47
 * @Author : 石冬冬-Seig Heil
 */
public class SerFinQueryLinkHandler extends AbstractQueryLinkHandler {
    protected SerFinRuleFacade serFinRuleFacade;
    /**
     * 构造函数
     */
    public SerFinQueryLinkHandler() {
        super("平台费规则", TagConstant.BuzTypeEnum.SER_FIN_RULE);
    }

    @Override
    protected void prepare(QueryLinkContext context) {
        super.prepare(context);
        serFinRuleFacade = context.getSerFinRuleFacade();
    }

    @Override
    public List<RateRule> queryLink(PageForm form) {
        return pdRuleProductService.querySerFinRules(form);
    }

    @Override
    public int queryLinkCount(PageForm form) {
        return pdRuleProductService.querySerFinRulesCount(form);
    }

    @Override
    List<BaseVo> convertVo() {
        return new SerFinRuleVoConvertor().convertList(entities);
    }

    @Override
    protected void call(QueryLinkContext context) {
        super.call(context);
        serFinRuleFacade.formatSerFinRule(entities, voList);
    }
}
2.6、QueryLinkHandlerFactory

产品工厂类,典型的工厂方法模式,其中抽象产品即AbstractQueryLinkHandler,通过create方法,以枚举区别生产相应产品,调用方无需关注调用何种产品。

/**
 * @description: 查询关联产品处理器工厂
 * @Date : 2018/11/20 下午6:03
 * @Author : 石冬冬-Seig Heil
 */
public final class QueryLinkHandlerFactory {
    final static int SECOND_YEAR = 2;
    final static int THIRD_YEAR = 3;
    /**
     * 创建处理器
     * @param buzTypeEnum
     * @return
     */
    public static final AbstractQueryLinkHandler create(BuzTypeEnum buzTypeEnum){
        switch (buzTypeEnum){
            case SER_FIN_RULE:
                return new SerFinQueryLinkHandler();
            case RATE_RULE:
                return new RateQueryLinkHandler();
            case GPS_RULE:
                return new GpsQueryLinkHandler();
            case INSURANSE_SECOND_YEAR:
                return new InsuranceQueryLinkHandler(SECOND_YEAR);
            case INSURANSE_THIRD_YEAR:
                return new InsuranceQueryLinkHandler(THIRD_YEAR);
            case ACCOUNT_RULE:
                return new AccountQueryLinkHandler();
            default:
                throw new BizException("illegal buzTypeEnum index = "+ buzTypeEnum.getIndex());
        }
    }
}

2.7、调用方

现在我们在调用方,只需要初始化上下文对象依赖的相关引用,并调用工厂类方法执行execute方法,最后通过context包装的count,voList返回调用发。

boolean switchEnable = configParamsFacade.getValueByParamKey("feeRuleProductLinkEnable",Boolean.class);
        if(switchEnable){
            QueryLinkContext context = QueryLinkContext.builder()
                    .yanbaoRuleFacade(yanbaoRuleFacade).commonComponent(commonComponent)
                    .pdRuleProductService(pdRuleProductService).serFinRuleFacade(serFinRuleFacade)
                    .form(form).build();
            QueryLinkHandlerFactory.create(buzTypeEnum).execute(context);
            return Result.suc(PageVO.newInstance(form.getDraw(),context.getCount(),context.getVoList()));
        }

归纳,我们通过引入模板方法模式,把原有switch case冗余的重复代码结构抽象出来,并通过工厂类负责产品的生产,调用发只需要调用工厂类,这样对于后期扩展,只需要增加子类,并在工厂类增加实现即可。工厂类,生产的产品,我们可以同样引入单例或者享元模式,减少对象实例的创建销毁。

下面的是我的公众号二维码图片,欢迎关注。
秋夜无霜

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值