目前系统应用中,有一个业务功能模块,是提供一个页面查询结果接口,通过参数控制以调用查询不同接口数据集,并对查询结果集做相应处理,封装并返回调用方。但是,该方法体使用了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方法,迭代每一个集合元素设置对应对象元素的标签属性。
但是上述代码我们同样可以看出有以下几个槽点:
方法中调用的一个对查询结果集设置标签的一个独立方法
/**
* 对相关查询的规则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
,BiConsumer ,通过提供函数式编程特性,摒弃传统的回调函数编码风格。
我们从上述章节【槽点分析】中,经过思路缜密分析,对于槽点1我们通过模板方法封装行为,对于槽点2我们可以把每一种case中作为抽象类的子类,对于那么多case,我们可以通过工厂模式封装。
从上述类图中我们看出,QueryLink是一个数据查询接口,AbstractQueryLinkHandler是一个抽象类,继承QueryLink接口,封装抽象行为,SerFinQueryLinkHandler等是对应switch case的场景,也就是要继承抽象类并实现相应的查询接口,QueryLinkContext是一个上线文对象,透传各个类交互传输,QueryLinkHandlerFactory负责生产Handler对象,交予调用发调用。
负责分页查询结果集及查询总条数,并规约查询结果集对象是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);
}
通过引入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;
}
该抽象类提供几个方法,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();
}
}
我们从重构前的代码中看出,对于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);
}
}
产品工厂类,典型的工厂方法模式,其中抽象产品即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());
}
}
}
现在我们在调用方,只需要初始化上下文对象依赖的相关引用,并调用工厂类方法执行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冗余的重复代码结构抽象出来,并通过工厂类负责产品的生产,调用发只需要调用工厂类,这样对于后期扩展,只需要增加子类,并在工厂类增加实现即可。工厂类,生产的产品,我们可以同样引入单例或者享元模式,减少对象实例的创建销毁。