使用策略模式消除if else代码

/ 默认分类 / 没有评论 / 642浏览

需求

业务需求是,有一个代报考系统,里面的一个功能是根据报考类目的不同维护不同的代报考规则。

代报考规则的实体:

public class ExamRuleEditReqDTO extends BaseDTO {

    // 无 则新增  有则 修改
    private Integer  id;
    //报考规则名称
    @NotNull(message = "报考规则名称不能为空!")
    private String  name;

    /**
     *  报考类目  typeCode
     *  消防工程师   HC_EXAM_TYPE_FIRE_ENGINEER
     *  成人高考  HC_EXAM_TYPE_ADULT_EXAM
     *  ACI 心理  HC_EXAM_TYPE_ACI_PSYCHOLOGY
     *  ACI 营养  HC_EXAM_TYPE_ACI_NUTRITION
     *  健康管理师 HC_EXAM_TYPE_HEALTH_MANAGER 
     *  自定义类目  HC_EXAM_TYPE_CUSTOMIZE
     */
    @NotNull(message = "报考类目Code不能为空!")
    private String  typeCode;
    //是否免协报费
    private String  exemptionAssistFlag;

    // ... 省略 get / set ...
    
 }   

service接口

public interface ExamService {

    //维护代报考规则
    ResponseBaseDTO<Integer> editExamRule(ExamRuleEditReqDTO reqDTO);

    //查询代报考规则
    ResponseBaseDTO<ExamRuleDetailQueryRespDTO> queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO);

}

传统实现

根据报考类目写一堆的if else

@Service
public class ExamServiceImpl implements ExamService {  
    @Autowired
    private ExamRuleFireEngineerHandler examRuleFireEngineerHandler;

    @Autowired
    private ExamRuleAdultExamHandler examRuleAdultExamHandler;

    @Autowired
    private ExamRuleAciPsychologyHandler examRuleAciPsychologyHandler;

    @Autowired
    private ExamRuleAciNutritionHandler examRuleAciNutritionHandler;

    @Autowired
    private ExamRuleHealthManagerHandler examRuleHealthManagerHandler;

    @Autowired
    private ExamRuleCustomizeHandler examRuleCustomizeHandler;

     public ResponseBaseDTO<Integer>  editExamRule(ExamRuleEditReqDTO reqDTO) {
      
        if (ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER.equals(reqDTO.getTypeCode())) {
            return examRuleFireEngineerHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ADULT_EXAM.equals(reqDTO.getTypeCode())) {
            return examRuleAdultExamHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY.equals(reqDTO.getTypeCode())) {
            return examRuleAciPsychologyHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION.equals(reqDTO.getTypeCode())) {
            return examRuleAciNutritionHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER.equals(reqDTO.getTypeCode())) {
            return examRuleHealthManagerHandler.editHandler(reqDTO);
        }
        return new ResponseBaseDTO<>(ResponseBaseDTO.FLAG_FAIL, "代报考类目参数不正确!");
    }
  
       public ResponseBaseDTO<ExamRuleDetailQueryRespDTO>  queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO) {
      
        if (ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER.equals(reqDTO.getTypeCode())) {
            return examRuleFireEngineerHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ADULT_EXAM.equals(reqDTO.getTypeCode())) {
            return examRuleAdultExamHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY.equals(reqDTO.getTypeCode())) {
            return examRuleAciPsychologyHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION.equals(reqDTO.getTypeCode())) {
            return examRuleAciNutritionHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER.equals(reqDTO.getTypeCode())) {
            return examRuleHealthManagerHandler.editHandler(reqDTO);
        } else if (ExamConstants.HC_EXAM_TYPE_CUSTOMIZE.equals(reqDTO.getTypeCode())) {
            return examRuleHealthManagerHandler.editHandler(reqDTO);
        }
        return new ResponseBaseDTO<>(ResponseBaseDTO.FLAG_FAIL, "代报考类目参数不正确!");
    }

}

策略模式实现

利用策略模式,只需要两行即可实现业务逻辑:

@Service
public class ExamServiceImpl implements ExamService {

  
    @Autowired
    private ExamRuleHandlerContext examRuleHandlerContext;
  
  
    @Override
    @Transactional
    public ResponseBaseDTO<Integer>  editExamRule(ExamRuleEditReqDTO reqDTO ) {
        return this.examRuleHandlerContext.getHandlerInstance(reqDTO.getTypeCode()).editHandler(reqDTO );
    }


    @Override
    public ResponseBaseDTO<ExamRuleDetailQueryRespDTO> queryExamRuleDetail(ExamRuleQueryReqDTO reqDTO) {
        return this.examRuleHandlerContext.getHandlerInstance(reqDTO.getTypeCode()).queryHandler(reqDTO);
    }

}

可以看到上面的方法中注入了ExamRuleHandlerContext,这是一个处理器上下文,用来保存不同的业务处理器,具体在下文会讲解。我们从中获取一个抽象的处理器AbstractExamRuleHandler,调用其方法实现业务逻辑。

现在可以了解到,我们主要的业务逻辑是在处理器中实现的,因此有多少个代报考类目,就对应有多少个处理器。以后需求变化,增加了代报考类目,只需要添加相应的处理器就可以,上述ExamServiceImpl完全不需改动。

业务处理器的写法

@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_CUSTOMIZE)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理自定义类目的规则
         return null;
    }
}


@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_HEALTH_MANAGER)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理健康管理师类目的规则
         return null;
    }
}


@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ACI_NUTRITION)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理ACI营养类目的规则
         return null;
    }
}


@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ACI_PSYCHOLOGY)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理ACI心理类目的规则
         return null;
    }
}


@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_ADULT_EXAM)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理成人高考类目的规则
         return null;
    }
}

 
@Component
@ExamRuleHandler(ExamConstants.HC_EXAM_TYPE_FIRE_ENGINEER)
public class ExamRuleCustomizeHandler extends AbstractExamRuleHandler {

    @Override
    public ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO){
         //处理消防工程师类目的规则
         return null;
    }
}


首先每个处理器都必须添加到spring容器中,因此需要加上@Component注解,其次需要加上一个自定义注解@ExamRuleHandler,用于标识该处理器对应哪个订单类型,最后就是继承AbstractExamRuleHandler,实现自己的业务逻辑。

自定义注解 @HandlerType


import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ExamRuleHandler {
    String value();
}

抽象处理器 AbstractHandler

public abstract class AbstractExamRuleHandler {

    public abstract ResponseBaseDTO<Integer> editHandler(ExamRuleEditReqDTO reqDTO );

    public abstract ResponseBaseDTO<ExamRuleDetailQueryRespDTO> queryHandler(ExamRuleQueryReqDTO reqDTO );
}

自定义注解和抽象处理器都很简单,那么如何将处理器注册到spring容器中呢? 具体思路是:

我们将核心的功能封装在ExamRuleHandlerContext类中,完成上面的功能。

上下文处理器ExamRuleHandlerContext

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
 import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class ExamRuleHandlerContext implements ApplicationContextAware {

    @Autowired
    ApplicationContext applicationContext;

     private static final Map<String,Class> handlerMap = new HashMap<>(10);

    public AbstractExamRuleHandler getHandlerInstance(String typeCode){
        Class clazz = handlerMap.get(typeCode);
        return (AbstractExamRuleHandler) applicationContext.getBean(clazz);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String,Object> beans = applicationContext.getBeansWithAnnotation(ExamRuleHandler.class);
        if (beans != null && beans.size() > 0) {
            for (Object serviceBean : beans.values()) {
                String payType = serviceBean.getClass().getAnnotation(ExamRuleHandler.class).value();
                handlerMap.put(payType, serviceBean.getClass());
            }
        }
    }
}

#getHandlerInstance方法根据类型获取对应的class,然后根据class类型获取注册到spring`中的bean。

最后请注意一点,ExamRuleHandlerContext 必须能被扫描到,或者通过@Bean的方式显式的注册,才能在项目启动时发挥作用。

总结

利用策略模式可以简化繁杂的if else代码,方便维护,而利用自定义注解和自注册的方式,可以方便应对需求的变更。本文只是提供一个大致的思路,还有很多细节可以灵活变化,例如使用枚举类型、或者静态常量,作为代报考的类型,相信你能想到更多更好的方法。