关于java:实战如何消除又臭又长的ifelse判断更优雅的编程

5次阅读

共计 8371 个字符,预计需要花费 21 分钟才能阅读完成。

最近在做代码重构,发现了很多代码的烂滋味。其余的不多说,明天次要说说那些又臭又长的 if…else 要如何重构。

在介绍更更优雅的编程之前,让咱们一起回顾一下,不好的 if…else 代码

一、又臭又长的 if…else

废话不多说,先看看上面的代码。

public interface IPay {  
​  
 void pay();}  
​  
​  
@Service  
public class AliaPay implements IPay {  
​  
​  
 @Override  
 public void pay() {System.out.println("=== 发动支付宝领取 ===");  
 }  
}  
​  
​  
@Service  
public class WeixinPay implements IPay {  
​  
 @Override  
 public void pay() {System.out.println("=== 发动微信领取 ===");  
 }  
}  
​  
​  
@Service  
public class JingDongPay implements IPay {  
​  
 @Override  
 public void pay() {System.out.println("=== 发动京东领取 ===");  
 }  
}  
​  
​
@Service  
public class PayService {  
​  
 @Autowired  
 private AliaPay aliaPay;  
 @Autowired  
 private WeixinPay weixinPay;  
 @Autowired  
 private JingDongPay jingDongPay;  
​  
​  
 public void toPay(String code) {if ("alia".equals(code)) {aliaPay.pay();  
 } else if ("weixin".equals(code)) {weixinPay.pay();  
 } else if ("jingdong".equals(code)) {jingDongPay.pay();  
 } else {System.out.println("找不到领取形式");  
 }  
 }  
}  
​

PayService 类的 toPay 办法次要是为了发动领取,依据不同的 code,决定调用用不同的领取类(比方:aliaPay)的 pay 办法进行领取。

这段代码有什么问题呢?兴许有些人就是这么干的。

试想一下,如果领取形式越来越多,比方:又加了百度领取、美团领取、银联领取等等,就须要改 toPay 办法的代码,减少新的 else…if 判断,判断多了就会导致逻辑越来越多?

很显著,这里守法了设计模式六大准则的:开闭准则 和  繁多职责准则

开闭准则:对扩大凋谢,对批改敞开。就是说减少新性能要尽量少改变已有代码。

繁多职责准则:顾名思义,要求逻辑尽量繁多,不要太简单,便于复用。

那有什么方法能够解决这个问题呢?

二、应用注解


代码中之所以要用 code 判断应用哪个领取类,是因为 code 和领取类没有一个绑定关系,如果绑定关系存在了,就能够不必判断了。

咱们先定义一个注解。

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface PayCode {  
​  
 String value();  
​  
 String name();}

而后在所有的领取类上都加上注解

@PayCode(value = "alia", name = "支付宝领取")  
@Service  
public class AliaPay implements IPay {  
​  
​  
 @Override  
 public void pay() {System.out.println("=== 发动支付宝领取 ===");  
 }  
}  
​  
​  
​  
@PayCode(value = "weixin", name = "微信领取")  
@Service  
public class WeixinPay implements IPay {  
​  
 @Override  
 public void pay() {System.out.println("=== 发动微信领取 ===");  
 }  
}  
​  
​  
@PayCode(value = "jingdong", name = "京东领取")  
@Service  
public class JingDongPay implements IPay {  
​  
 @Override  
 public void pay() {System.out.println("=== 发动京东领取 ===");  
 }  
}

而后减少最要害的类:

@Service  
public class PayService2 implements ApplicationListener<ContextRefreshedEvent> {  
​  
 private static Map<String, IPay> payMap = null;  
​  
 @Override  
 public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();  
 Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);  
​  
 if (beansWithAnnotation != null) {payMap = new HashMap<>();  
 beansWithAnnotation.forEach((key, value) -> {String bizType = value.getClass().getAnnotation(PayCode.class).value();  
 payMap.put(bizType, (IPay) value);  
 });  
 }  
 }  
​  
 public void pay(String code) {payMap.get(code).pay();}  
}

PayService2 类实现了 ApplicationListener 接口,这样在 onApplicationEvent 办法中,就能够拿到 ApplicationContext 的实例。咱们再获取打了 PayCode 注解的类,放到一个 map 中,map 中的 key 就是 PayCode 注解中定义的 value,跟 code 参数统一,value 是领取类的实例。

这样,每次就能够每次间接通过 code 获取领取类实例,而不必 if…else 判断了。如果要加新的领取办法,只需在领取类下面打上 PayCode 注解定义一个新的 code 即可。

留神:这种形式的 code 能够没有业务含意,能够是纯数字,只有不反复就行。

三、动静拼接名称

再看看这种办法,次要针对 code 是有业务含意的场景。

@Service  
public class PayService3 implements ApplicationContextAware {  
​  
 private ApplicationContext applicationContext;  
​  
 private static final String SUFFIX = "Pay";  
​  
 @Override  
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}  
​  
 public void toPay(String payCode) {((IPay) applicationContext.getBean(getBeanName(payCode))).pay();}  
​  
 public String getBeanName(String payCode) {return payCode + SUFFIX;}  
​  
}

咱们能够看到,领取类 bean 的名称是由 code 和后缀拼接而成,比方:aliaPay、weixinPay 和 jingDongPay。这就要求领取类取名的时候要特地留神,后面的一段要和 code 保持一致。调用的领取类的实例是间接从 ApplicationContext 实例中获取的,默认状况下 bean 是单例的,放在内存的一个 map 中,所以不会有性能问题。

特地阐明一下,这种办法实现了 ApplicationContextAware 接口跟下面的 ApplicationListener 接口不一样,是想通知大家获取 ApplicationContext 实例的办法不只一种。

四、模板办法判断

当然除了下面介绍的两种办法之外,spring 的源码实现中也通知咱们另外一种思路,解决 if…else 问题。

咱们先一起看看 spring AOP 的局部源码,看一下 DefaultAdvisorAdapterRegistry 的 wrap 办法

 public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {if (adviceObject instanceof Advisor) {return (Advisor) adviceObject;  
 }  
 if (!(adviceObject instanceof Advice)) {throw new UnknownAdviceTypeException(adviceObject);  
 }  
 Advice advice = (Advice) adviceObject;  
 if (advice instanceof MethodInterceptor) {  
 // So well-known it doesn't even need an adapter.  
 return new DefaultPointcutAdvisor(advice);  
 }  
 for (AdvisorAdapter adapter : this.adapters) {  
 // Check that it is supported.  
 if (adapter.supportsAdvice(advice)) {return new DefaultPointcutAdvisor(advice);  
 }  
 }  
 throw new UnknownAdviceTypeException(advice);  
 }

重点看看 supportAdvice 办法,有三个类实现了这个办法。咱们轻易抽一个类看看

class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {  
​  
 @Override  
 public boolean supportsAdvice(Advice advice) {return (advice instanceof AfterReturningAdvice);  
 }  
​  
 @Override  
 public MethodInterceptor getInterceptor(Advisor advisor) {AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();  
 return new AfterReturningAdviceInterceptor(advice);  
 }  
​  
}

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

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

其实,咱们能够这样做,定义一个接口或者抽象类,外面有个 support 办法判断参数传的 code 是否本人能够解决,如果能够解决则走领取逻辑。

public interface IPay {  
​  
 boolean support(String code);  
​  
 void pay();}  
​  
​  
@Service  
public class AliaPay implements IPay {  
​  
 @Override  
 public boolean support(String code) {return "alia".equals(code);  
 }  
​  
 @Override  
 public void pay() {System.out.println("=== 发动支付宝领取 ===");  
 }  
}  
​  
​  
@Service  
public class WeixinPay implements IPay {  
​  
​  
 @Override  
 public boolean support(String code) {return "weixin".equals(code);  
 }  
​  
 @Override  
 public void pay() {System.out.println("=== 发动微信领取 ===");  
 }  
}  
​  
​  
@Service  
public class JingDongPay implements IPay {  
 @Override  
 public boolean support(String code) {return "jingdong".equals(code);  
 }  
​  
 @Override  
 public void pay() {System.out.println("=== 发动京东领取 ===");  
 }  
}

每个领取类都有一个 support 办法,判断传过来的 code 是否和本人定义的相等。

@Service  
public class PayService4 implements ApplicationContextAware, InitializingBean {  
​  
 private ApplicationContext applicationContext;  
​  
 private List<IPay> payList = null;  
​  
 @Override  
 public void afterPropertiesSet() throws Exception {if (payList == null) {payList = new ArrayList<>();  
 Map<String, IPay> beansOfType = applicationContext.getBeansOfType(IPay.class);  
​  
 beansOfType.forEach((key, value) -> payList.add(value));  
 }  
 }  
​  
 @Override  
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}  
​  
 public void toPay(String code) {for (IPay iPay : payList) {if (iPay.support(code)) {iPay.pay();  
 }  
 }  
 }  
​  
}

这段代码中先把实现了 IPay 接口的领取类实例初始化到一个 list 汇合中,返回在调用领取接口时循环遍历这个 list 汇合,如果 code 跟本人定义的一样,则调用以后的领取类实例的 pay 办法。

五、其余的打消 if…else 的办法

当然理论我的项目开发中应用 if…else 判断的场景十分多,下面只是其中几种场景。上面再列举一下,其余常见的场景。

1. 依据不同的数字返回不同的字符串

public String getMessage(int code) {if (code == 1) {return "胜利";} else if (code == -1) {return "失败";} else if (code == -2) {return "网络超时";} else if (code == -3) {return "参数谬误";}  
 throw new RuntimeException("code 谬误");  
}

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

public enum MessageEnum {  
​  
 SUCCESS(1, "胜利"),  
 FAIL(-1, "失败"),  
 TIME_OUT(-2, "网络超时"),  
 PARAM_ERROR(-3, "参数谬误");  
​  
 private int code;  
 private String message;  
​  
 MessageEnum(int code, String message) {  
 this.code = code;  
 this.message = message;  
 }  
​  
 public int getCode() {return this.code;}  
​  
 public String getMessage() {return this.message;}  
​  
 public static MessageEnum getMessageEnum(int code) {return Arrays.stream(MessageEnum.values()).filter(x -> x.code == code).findFirst().orElse(null);  
 }  
}

再把调用办法略微调整一下

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

完满。

2. 汇合中的判断

下面的枚举 MessageEnum 中的 getMessageEnum 办法,如果不必 java8 的语法的话,可能要这样写

public static MessageEnum getMessageEnum(int code) {for (MessageEnum messageEnum : MessageEnum.values()) {if (code == messageEnum.code) {return messageEnum;}  
 }  
 return null;  
}

对于汇合中过滤数据,或者查找办法,java8 有更简略的办法打消 if…else 判断。

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

3. 简略的判断

其实有些简略的 if…else 齐全没有必要写,能够用三目运算符代替,比方这种状况:

public String getMessage2(int code) {if(code == 1) {return  "胜利";}  
 return "失败";  
}

改成三目运算符:

public String getMessage2(int code) {return code == 1 ? "胜利" : "失败";}

批改之后代码更简洁一些。

4. 判断是否为 null

java 中自从有了 null 之后,很多中央都要判断实例是否为 null,不然可能会呈现 NPE 的异样。

 public String getMessage2(int code) {return code == 1 ? "胜利" : "失败";}  
​  
 public String getMessage3(int code) {  
 Test test = null;  
 return test.getMessage2(1);  
 }

这里如果不判断异样的话,就会呈现 NPE 异样。咱们只能老老实实加上判断。

public String getMessage3(int code) {  
 Test test = null;  
 if (test != null) {return test.getMessage2(1);  
 }  
 return null;  
}

有没有其余更优雅的解决形式呢?

public String getMessage3(int code) {  
 Test test = null;  
 Optional<Test> testOptional = Optional.of(test);  
 return testOptional.isPresent() ? testOptional.get().getMessage2(1) : null;  
}

答案是应用 Optional

当然,还有很多其余的场景能够优化 if…else,我再这里就不一一介绍了,感兴趣的敌人能够给我留言,一起探讨和钻研一下。

如果这篇文章对您有帮忙或者有所启发的话,请帮忙扫码关注上面的公众账号。原创不易,谢谢您的反对。

举荐浏览

  • 面试前看了这篇 spring 事务的文章,让我多要了 2k 的工资
  • spring 事务的这 10 种坑,你稍不留神可能就会踩中!!![](http://mp.weixin.qq.com/s?__b…
  • spring 解决循环依赖为什么要用三级缓存
  • java8 stream 的这些开发技巧,你值得好好珍藏
  • mq 要如何解决音讯失落、反复生产?
  • 什么是缓存击穿、雪崩、穿透
  • 揭开 ThreadLocal 的神秘面纱
  • 面试官:spring 的 BeanFatory 和 FactoryBean 区别
正文完
 0