消除 if-else 带来的臃肿代码 让代码更简洁更优雅

 


有时候写多了if-else自己都会烦, 为什么别人的代码这么简洁, 而自己写出来的代码这么的臃肿,一堆一堆的,看起来很难受,这里我们介绍几种比较好的方法可以让你代码更简洁优雅一点

 

首先举个业务案例

        需求:我们都知道一个请求过来, 请求头会带着Content-Type这个字段, 我们这里就是根据这个Content-Type的类型来做不同的内容解析, 如果匹配到Content-Type=json 类型我们就输出一条json相关的输出信息, 如果匹配到Content-Type=xml 类型我们就输出一条xml相关的输出信息

       Content-Type定义两个枚举值如下, json类型代表我们要按照json格式输出, xml类型代表我们要按照xml格式输出

@Getter
public enum ContentType {
    /**代表application/json请求类型,内容应该按照json处理解析*/
    APPLICATION_JSON("json"),

    /**代表application/xml请求类型,内容应该按照xml处理解析*/
    APPLICATION_XML("xml");

    private final String code;

    ContentType(final String code) {
        this.code = code;
    }
}

 
定义好了上面的枚举, 我们开始定义我们真正开始要执行的业务操作,Service以及两个ServiceImpl

  • JsonContentResolverServiceImpl 对Content-Type=json类型的做内容解析
  • XmlContentResolverServiceImpl对Content-Type=xml类型的做内容解析
public interface ContentResolverService {
    void doResolver(NativeRequest request);
}

@Service
public class JsonContentResolverServiceImpl implements ContentResolverService {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
    }
}

@Service
public class XmlContentResolverServiceImpl implements ContentResolverService {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
    }
}

 
接着我们要开始组装这个业务(根据Content-Type类型对应进行不同的处理解析内容)

        如下图所示:我们会发现这extracted方法会产生很多的判断if-else判断条件,设想下有很多的Content-Type是不是就要写n多的if-else, 而且还要改源代码,破坏了代码的开闭原则,写起来也很烦!

    @Autowired
    private JsonContentResolverServiceImpl jsonService;
    @Autowired
    private XmlContentResolverServiceImpl xmlService;

    private void extracted(SendMqRequest request) {

        if (request.getContentType().equals(ContentType.APPLICATION_JSON.getCode())) {
            jsonService.doResolver(NativeRequest.builder()
                    .contentType(ContentType.APPLICATION_JSON.getCode())
                    .build());

        } else if(request.getContentType().equals(ContentType.APPLICATION_XML.getCode())) {
            xmlService.doResolver(NativeRequest.builder()
                    .contentType(ContentType.APPLICATION_XML.getCode())
                    .build());
        } else {
            //TODO...
        }
    }

如果想要消除这些if-else,其实我们知道if-else,就是根据某个条件获取正确一个值,有了这个概念, 我么会想到map集合,这个是最快获取一个等值条件的容器,它可以帮你的条件和你希望返回的值做了一 一映射关系,
 
那我们如何将这些条件和bean做一个绑定关系?
如果要想绑定得拿到我们的bean,获取到bean得话,做条件绑定关系就很简单

 

自定义注解模式(结合事件监听机制)

我们可以把我们的条件(Content-Type)定义成注解,因为我们要把这个条件作为map得key,然后value放对应得bean, 如下所示:

/**
 * 这个注解主要是给我们Spring容器中的bean重新起个别名
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ContentTypeCode {
    String contentType() default "";
}

然后在所有得ContentResolverService实现类添加这个注解

@Service
@ContentTypeCode(contentType = "applicationJson")
public class JsonContentResolverServiceImpl implements ContentResolverService {

    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
    }
}

@Service
@ContentTypeCode(contentType = "applicationXml")
public class XmlContentResolverServiceImpl implements ContentResolverService {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
    }
}

然后增加最关键的类,我们要首先要获取到bean,以及这个bean对应得自定义注解(其实就是我们得Content-Type条件),这个bean和注解以及条件都有了,我们就 可以在 map中绑定关系了

获取bean得方式有很多中,我这是其中一种,实现接口监听ApplicationListener, 监听Spring得ContextRefreshedEvent事件,这个事件在已经把bean都已经加载完成了,这样在onApplicationEvent方法中,就可以拿到ApplicationContext的实例

getBeansWithAnnotation根据注解获取标记得bean

@Service
public class TransInnerSerivceImpl 
implements TransInnerService, 
		   ApplicationListener<ContextRefreshedEvent> {

    private void extracted(SendMqRequest request) {
    	//一行代码就完事了
        gobalMap.get(request.getContentType())
                .doResolver(NativeRequest.builder().contentType(request.getContentType()).build());
	}

    private static Map<String,ContentResolverService> gobalMap = new HashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        ApplicationContext context = event.getApplicationContext();
        Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(ContentTypeCode.class);

        Optional.ofNullable(beansWithAnnotation).ifPresent(
                e->{
                    e.forEach((k,v) ->{
                        String contentTypeKey = v.getClass().getAnnotation(ContentTypeCode.class).contentType();
                        System.out.println("contentTypekey====>"+contentTypeKey);
                        gobalMap.put(contentTypeKey, (ContentResolverService)v);
                    });
                }
        );
    }
}

这样每次新加一种Content-Type=Text都只需要在实现类上加上注解即可,什么都不用做了

注意: 这种方式的注解可以没有业务含义,只要不重复就行。

 

命名约束模式(实现ApplicationContextAware接口)

其实就是相当于注入一个ApplicationContext, 也可以直接使用@Autowire注入更快

    //@Autowired
    private ApplicationContext context;

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

然后就是通过ApplicationContext.getBean("实现类得名字"),这里注意一下,我们得实现类我们得自己给他一个名字,就按照你的条件类型来命名即可,如下所示:

@Service("applicationXml")
public class XmlContentResolverServiceImpl implements ContentResolverService {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
    }
}

@Service("applicationJson")
public class JsonContentResolverServiceImpl implements ContentResolverService {

    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
    }

}

按照一定得命名之后, 我们就可以直接通过ApplicationContext.getBean("命名")获取对应的bean并调用其方法了,比如我们这里request中 contentType 传入applicationJson,或者applicationXml都可以获取到对应得实现类bean,但是传入其他类型的就不能获取了,因为我们这两个名称applicationXmlapplicationJsonXmlContentResolverServiceImplJsonContentResolverServiceImpl 类中标记过

    private void extracted(SendMqRequest request) {

        ((ContentResolverService)context.getBean(request.getContentType())).doResolver(NativeRequest.builder()
                .contentType(request.getContentType())
                .build());

    }

注意:这个约束性比较大 ,主要是自己得规则自己知道就行,就怕被人帮你给改了下就完蛋

 

工厂模式及策略模式

原理其实还是一样得,就是我们还是得获取到已经完成装载得 bean,然后想方设法把bean获取到,做map得绑定关系

先定义一个全局的工厂类,绑定方法register


public class MapFactory {

    public static Map<String, ContentResolverService> serviceMap = new HashMap<>();

    public static void register(String contentType, ContentResolverService bean) {
        serviceMap.put(contentType, bean);
    }
}

然后每个实现类实现接口InitializingBean,这个接口会有个方法afterPropertiesSet 这个方法是在bean装载完成之后可以加载的方法,所以这里我们可以做map绑定关系,如下所示:

@Service
public class XmlContentResolverServiceImpl implements ContentResolverService, InitializingBean {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("XmlContentResolverServiceImpl 已经被Spring加载好了");
        MapFactory.register(ContentType.APPLICATION_XML.getCode(), this);
    }
}

@Service
public class JsonContentResolverServiceImpl implements ContentResolverService, InitializingBean {

    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("JsonContentResolverServiceImpl 已经被Spring加载好了");
        MapFactory.register(ContentType.APPLICATION_JSON.getCode(), this);
    }
}

然后我们在逻辑里面直接根据条件获取对应的实现类并调用方法

private void extracted(SendMqRequest request) {
        MapFactory.serviceMap.get(request.getContentType()).doResolver(NativeRequest.builder()
                .contentType(request.getContentType())
                .build());
}

这里用的是最简单的工厂类作为案例

 

模板方法模式

我们先一起看看spring AOP的部分源码,看一下DefaultAdvisorAdapterRegistrywrap方法


    public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
        if (adviceObject instanceof Advisor) {
            return (Advisor)adviceObject;
        } else if (!(adviceObject instanceof Advice)) {
            throw new UnknownAdviceTypeException(adviceObject);
        } else {
            Advice advice = (Advice)adviceObject;
            if (advice instanceof MethodInterceptor) {
                return new DefaultPointcutAdvisor(advice);
            } else {
                Iterator var3 = this.adapters.iterator();

                AdvisorAdapter adapter;
                do {
                    if (!var3.hasNext()) {
                        throw new UnknownAdviceTypeException(advice);
                    }

                    adapter = (AdvisorAdapter)var3.next();
                } while(!adapter.supportsAdvice(advice));

                return new DefaultPointcutAdvisor(advice);
            }
        }
    }

重点看看supportAdvice方法,有三个类实现(AfterReturningAdviceAdapter,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter)了这个方法。我们随便抽一个类看看

class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
    AfterReturningAdviceAdapter() {
    }

    public boolean supportsAdvice(Advice advice) {
        return advice instanceof AfterReturningAdvice;
    }

    public MethodInterceptor getInterceptor(Advisor advisor) {
        AfterReturningAdvice advice = (AfterReturningAdvice)advisor.getAdvice();
        return new AfterReturningAdviceInterceptor(advice);
    }
}

该类的supportsAdvice方法非常简单,只是判断了一下advice的类型是不是AfterReturningAdvice

我们看到这里应该有所启发。

其实,我们可以这样做,定义一个接口或者抽象类,里面有个support方法判断参数传的Content-Type是否自己可以处理,如果可以处理则走内容解析逻辑。

先声明接口,提供bool得support方法

public interface ContentResolverService {
    void doResolver(NativeRequest request);

    boolean support(String contentType);
}

然后实现类如下

@Service
public class JsonContentResolverServiceImpl implements ContentResolverService {

    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
    }

    @Override
    public boolean support(String contentType) {
        return ContentType.APPLICATION_JSON.getCode().equals(contentType);
    }

}
@Service
public class XmlContentResolverServiceImpl implements ContentResolverService {
    @Override
    public void doResolver(NativeRequest request) {
        System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
    }

    @Override
    public boolean support(String contentType) {
        return ContentType.APPLICATION_XML.getCode().equals(contentType);
    }
}


我们在声明一个组装类,这是一个关键得类, 但是方法其实上面已经讲到了,主要是怎么获取到bean,然后取出来之后做绑定关系,实现接口InitializingBean, 实现接口ApplicationContextAware(这里我直接使用@Autowire 注入方便快捷, 其实我们看源码得时候 有很多时候都是这种方式实现代码(SpringBoot视图解析器)

注意: 一般得 和业务不想关得业务可以抽离出去,不要和业务代码混在一起

@Service
public class ContentResolverServiceCompsite implements InitializingBean {

    private List<ContentResolverService> list = Lists.newArrayList();

    @Autowired
    private ApplicationContext context;

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, ContentResolverService> beansOfType = context.getBeansOfType(ContentResolverService.class);
        beansOfType.forEach((k,v)->{

            list.add(v);
        });
    }

    public void doResolver(String contentType) {
    
        list.forEach(e->{
            if (e.support(contentType))
                e.doResolver(NativeRequest.builder().contentType(contentType).build());
        });
    }
}

这段代码中先把实现了ContentResolverService 接口的类实例初始化到一个list集合中,返回在调用ContentResolverService 接口时循环遍历这个list集合,如果Content-Type跟自己定义的一样,则调用当前的实现类实例的doResolver方法。

 

枚举模式(根据不同的数字返回不同的字符串)

        if (1 == request.getCode()) {
            return "one";
        } else if (2 == request.getCode()) {
            return "two";
        } else {
            return "three";
        }

其实,这种判断没有必要,用一个枚举就可以搞定。

@Getter
public enum MessageEnum {

    ONE(1,"one"),
    TWO(2,"two"),
    THREE(3,"three");

    private final int code;
    private final String message;

    MessageEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public static MessageEnum getMessageEnum(int code) {
        return Arrays.stream(MessageEnum.values())
                .filter(e->e.code == code)
                .findFirst()
                .orElse(null);
    }
}

再把调用方法稍微调整一下

    public String  getMessage(int code) {
    
        return MessageEnum.getMessageEnum(code).getMessage();
    }
  

后续有更好的办法也会持续更新进去…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值