策略模式+简单工厂+注解消除 if-else/switch-case

在很多时候,我们代码中会有很多分支,而且分支下面的代码又有一些复杂的逻辑,相信很多人都喜欢用 if-else/switch-case 去实现。做的不好的会直接把实现的代码放在 if-else/switch-case 的分支之下:

  1. switch ( type ) {
  2. case case1:
  3. ...
  4. ...
  5. break;
  6. case case2:
  7. ...
  8. ...
  9. break;
  10. case case3:
  11. ...
  12. ...
  13. break
  14. default:
  15. return null;
  16. }

这样的代码不仅冗长,读起来也非常困难。做的好一点的会把这些逻辑封装成函数然后在分支中调用:

  1. switch ( type ) {
  2. case case1:
  3. return case1Func();
  4. case case2:
  5. return case2Func();
  6. case case3:
  7. return case3Func();
  8. default:
  9. return null;
  10. }

即使这样也是面向过程思维的写法,毫无设计模式可言。不仅违背开闭原则,而且随着 switch-case 分支的增多,该段代码只会越来越冗长。其实这种代码已经有成熟的模式去消除诸多的 if-else/switch-case 分支,即使用 注解+策略模式+简单工厂的方式** **消除 if-else/switch-case 。

1. 定义一个枚举类

  1. public enum TagEvent {
  2. /**
  3. * 文件
  4. */
  5. TAG_FILE(1),
  6. /**
  7. * paper
  8. */
  9. TAG_PAPER(2),
  10. /**
  11. * 课件
  12. */
  13. TAG_COURSEWARE(3),
  14. /**
  15. * book
  16. */
  17. TAG_BOOK(4),
  18. /**
  19. * 个人book模板
  20. */
  21. TAG_BOOKTEMPLATE(5),
  22. /**
  23. * 游戏
  24. */
  25. TAG_GAME(6),
  26. /**
  27. * 物理实验
  28. */
  29. TAG_PHYSICALEXPERIMENT(7);
  30. private Integer tag;
  31. TagEvent(Integer tag) {
  32. this.tag = tag;
  33. }
  34. public Integer getTag() {
  35. return tag;
  36. }
  37. public void setTag(final Integer tag) {
  38. this.tag = tag;
  39. }
  40. public static TagEvent getEvent(Integer tag) {
  41. for (TagEvent value : TagEvent.values()) {
  42. if (value.getTag().equals(tag)) {
  43. return value;
  44. }
  45. }
  46. return null;
  47. }
  48. }

2. 自定义 TagEventAnnotation 注解

  1. /**
  2. * @description: 自定义文档标签注解
  3. * @author: zcq
  4. * @date: 2020/6/8 2:15 下午
  5. */
  6. @Target(ElementType.TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface TagEventAnnotation {
  9. TagEvent value();
  10. }

3. 自定义 TagEventProcess 接口

  1. /**
  2. * @description: 文档标签 事件接口
  3. * @author: zcq
  4. * @date: 2020/6/9 2:17 下午
  5. */
  6. public interface TagEventProcess {
  7. /**
  8. * @description: 获取文档 url
  9. * @author: zcq
  10. * @date: 2020/6/9 2:20 下午
  11. */
  12. String getDocumentUrl(TagEventDto document) throws RestClientException;
  13. /**
  14. * @description: 获取分享 url
  15. * @author: zcq
  16. * @date: 2020/6/9 2:53 下午
  17. */
  18. String publicShare(TagEventDto eventDto) throws RestClientException;
  19. /**
  20. * @description: 修改名称
  21. * @author: zcq
  22. * @date: 2020/6/9 3:51 下午
  23. */
  24. Result modifyName(TagEventDto eventDto) throws ServiceException;
  25. /**
  26. * @description: 文档复制
  27. * @author: zcq
  28. * @date: 2020/6/9 4:20 下午
  29. */
  30. void copyDocument(TagEventDto eventDto) throws ServiceException;
  31. }


4. 接口实现类

每一个标签类型一个实现类,实现 TagEventProcess 接口,比如:

/**
 * @description: book 标签 事件处理类
 * @author: zcq
 * @date: 2020/6/9 2:01 下午
 */
@Slf4j
@Component
@TagEventAnnotation(TagEvent.TAG_BOOK)
public class TagBookProcess implements TagEventProcess {

    @Autowired
    private FilePropResource filePropResource;

    @Autowired
    private BookRestClient bookRestClient;

    /**
     * @description: 获取 book url
     * @author: zcq
     * @date: 2020/6/9 2:21 下午
     */
    @Override
    public String getDocumentUrl(TagEventDto eventDto) throws RestClientException {
        String url = eventDto.getFileRestClient().getBookIndexUrl(eventDto.getDocument().getId());
        return DocumentPermission.canEdit(eventDto.getPermission()) ? url : url + "&isBrower=1";
    }

    /**
     * @param eventDto
     * @description: 获取分享 url
     * @author: zcq
     * @date: 2020/6/9 2:53 下午
     */
    @Override
    public String publicShare(TagEventDto eventDto) throws RestClientException {
        return null;
    }

    /**
     * @param eventDto
     * @description: 修改名称
     * @author: zcq
     * @date: 2020/6/9 3:51 下午
     */
    @Override
    public Result modifyName(TagEventDto eventDto) throws ServiceException {
        eventDto.getParams().put("id", eventDto.getDocumentDto().getId());
        eventDto.getParams().put("title", eventDto.getDocumentDto().getName());
        return renameBook(eventDto.getParams());
    }

    /**
     * @param eventDto
     * @description: 文档复制
     * @author: zcq
     * @date: 2020/6/9 4:20 下午
     */
    @Override
    public void copyDocument(TagEventDto eventDto) throws ServiceException {
        eventDto.getParams().put("id", eventDto.getDocumentDto().getId());
        eventDto.setResult(copyBook(eventDto.getParams()));
        Map<String, String> resultMap = (Map) eventDto.getResult().getData();
        eventDto.getDocument().setId(resultMap.get("id"));
        eventDto.getDocument().setName(resultMap.get("title"));
    }

    private Result renameBook(Map params) throws ServiceException {
        try {
            ValidParameterUtils.validParamNotNull(
                    params.get("id"),
                    params.get("title")
            );
            String token = ValidParameterUtils.getRestParamString(params, filePropResource.getFileRestToken());
            params.put("token", token);

            ParamsDto paramsDto = new ParamsDto();
            Map paramsDtoMap = BeanUtils.bean2Map(paramsDto);
            params.putAll(paramsDtoMap);
            Result result = bookRestClient.renameBook(params);
            return result;
        } catch (RestClientException e) {
            String errorMessage = "重命名book失败!";
            log.error("调用file rest接口 重命名book失败!", e);
            throw new ServiceException(errorMessage, e);
        }
    }

    private Result copyBook(Map params) throws ServiceException {
        try {
            ValidParameterUtils.validParamNotNull(
                    params.get("id")
            );

            String token = ValidParameterUtils.getRestParamString(params, filePropResource.getFileRestToken());
            params.put("token", token);

            ParamsDto paramsDto = new ParamsDto();
            Map paramsDtoMap = BeanUtils.bean2Map(paramsDto);
            params.putAll(paramsDtoMap);
            Result result = bookRestClient.copyBook(params);
            return result;
        } catch (RestClientException e) {
            String errorMessage = "复制book失败!";
            log.error("调用file rest接口 复制book失败!", e);
            throw new ServiceException(errorMessage, e);
        }
    }
}

5. 参数封装DTO

/**
 * @description: 标签事件请求参数封装
 * @author: zcq
 * @date: 2020/6/9 3:36 下午
 */
@Data
@Builder
public class TagEventDto {
    private Document document;
    private FileRestClient fileRestClient;
    private Integer permission;
    private UserVo userVo;
    private String id;
    private String url;
    private Map<String, Object> params;
    private DocumentDto documentDto;
    private Result result;
    private FileDto fileDto;
}

6. SpringContextUtil 工具类

/**
 * @description: 从 Spring context 获取 bean 的工具类
 * @author: zcq
 * @date: 2020/6/9 1:58 下午
 */
@Component
public class SpringContextUtil implements ApplicationContextAware {

    private ApplicationContext context;

    public ApplicationContext getContext() {
        return context;
    }

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

7. 事件工厂类

/**
 * @description: 文档标签事件工厂类
 * @author: zcq
 * @date: 2020/6/9 1:57 下午
 */
@Component
public class EventProcessFactory {

    @Resource
    SpringContextUtil springContextUtil;

    private static Map<TagEvent, TagEventProcess> eventProcessMap = Maps.newConcurrentMap();

    @PostConstruct
    public void init() {
        Map<String, Object> beanMap = springContextUtil.getContext().getBeansWithAnnotation(TagEventAnnotation.class);

        for (Object eventProcess : beanMap.values()) {
            TagEventAnnotation annotation = eventProcess.getClass().getAnnotation(TagEventAnnotation.class);
            eventProcessMap.put(annotation.value(), (TagEventProcess) eventProcess);
        }
    }

    public static TagEventProcess createEventProcess(TagEvent event) {
        return eventProcessMap.get(event);
    }
}

8. 调用

之前调用方式:
image.png
改造之后:

String url;
// 根据文档的标签类型获取对应分享的 url
TagEventProcess eventProcess = EventProcessFactory.createEventProcess(TagEvent.getEveny(document.getTag()));
if (eventProcess != null) {
    TagEventDto eventDto = TagEventDto.builder()
        .document(document).id(id).build();
    url = eventProcess.publicShare(eventDto);
} else {
    throw new Exception("此类型的文件不能分享");
}