乐趣区

关于后端:9条消除ifelse的锦囊妙计助你写出更优雅的代码

前言

最近在做代码重构,发现了很多代码的烂滋味。其余的不多说,明天次要说说那些又臭又长的 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();  
         } elseif ("weixin".equals(code)) {weixinPay.pay();  
         } elseif ("jingdong".equals(code)) {jingDongPay.pay();  
         } else {System.out.println("找不到领取形式");  
         }  
     }  
}
复制代码

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

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

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

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

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

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

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

二、打消 if…else 的神机妙算

1、应用注解

代码中之所以要用 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 能够没有业务含意,能够是纯数字,只有不反复就行。

2、动静拼接名称

该办法次要针对 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 实例的办法不只一种。

3、模板办法判断

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

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

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {if (adviceObject instanceof Advisor) {return (Advisor) adviceObject;  
     }  
     if (!(adviceObject instanceof Advice)) {thrownew UnknownAdviceTypeException(adviceObject);  
     }  
     Advice advice = (Advice) adviceObject;  
     if (advice instanceof MethodInterceptor) {returnnew DefaultPointcutAdvisor(advice);  
     }  
     for (AdvisorAdapter adapter : this.adapters) {if (adapter.supportsAdvice(advice)) {returnnew DefaultPointcutAdvisor(advice);  
         }  
     }  
     thrownew 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();  
        returnnew 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 办法。

4. 策略 + 工厂模式

这种形式也是用于 code 是有业务含意的场景。

策略模式定义了一组算法,把它们一个个封装起来, 并且使它们可互相替换。工厂模式用于封装和治理对象的创立,是一种创立型模式。

public interface IPay {void pay();
}

@Service
public class AliaPay implements IPay {

    @PostConstruct
    public void init() {PayStrategyFactory.register("aliaPay", this);
    }


    @Override
    public void pay() {System.out.println("=== 发动支付宝领取 ===");
    }

}

@Service
public class WeixinPay implements IPay {

    @PostConstruct
    public void init() {PayStrategyFactory.register("weixinPay", this);
    }

    @Override
    public void pay() {System.out.println("=== 发动微信领取 ===");
    }
}

@Service
public class JingDongPay implements IPay {

    @PostConstruct
    public void init() {PayStrategyFactory.register("jingDongPay", this);
    }

    @Override
    public void pay() {System.out.println("=== 发动京东领取 ===");
    }
}

public class PayStrategyFactory {private static Map<String, IPay> PAY_REGISTERS = new HashMap<>();


    public static void register(String code, IPay iPay) {if (null != code && !"".equals(code)) {PAY_REGISTERS.put(code, iPay);
        }
    }


    public static IPay get(String code) {return PAY_REGISTERS.get(code);
    }
}

@Service
public class PayService3 {public void toPay(String code) {PayStrategyFactory.get(code).pay();}
}
复制代码

这段代码的要害是 PayStrategyFactory 类,它是一个策略工厂,外面定义了一个全局的 map,在所有 IPay 的实现类中注册以后实例到 map 中,而后在调用的中央通过 PayStrategyFactory 类依据 code 从 map 获取领取类实例即可。

5. 责任链模式

这种形式在代码重构时用来打消 if…else 十分无效。

责任链模式:将申请的解决对象像一条长链个别组合起来,造成一条对象链。申请并不知道具体执行申请的对象是哪一个,这样就实现了申请与解决对象之间的解耦。罕用的 filterspring aop 就是应用了责任链模式,这里我略微改进了一下,具体代码如下:

public abstract class PayHandler {

    @Getter
    @Setter
    protected PayHandler next;

    public abstract void pay(String pay);

}

@Service
public class AliaPayHandler extends PayHandler {


    @Override
    public void pay(String code) {if ("alia".equals(code)) {System.out.println("=== 发动支付宝领取 ===");
        } else {getNext().pay(code);
        }
    }

}

@Service
public class WeixinPayHandler extends PayHandler {

    @Override
    public void pay(String code) {if ("weixin".equals(code)) {System.out.println("=== 发动微信领取 ===");
        } else {getNext().pay(code);
        }
    }
}

@Service
public class JingDongPayHandler extends PayHandler {


    @Override
    public void pay(String code) {if ("jingdong".equals(code)) {System.out.println("=== 发动京东领取 ===");
        } else {getNext().pay(code);
        }
    }
}

@Service
public class PayHandlerChain implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;
    private PayHandler header;


    public void handlePay(String code) {header.pay(code);
    }

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

    @Override
    public void afterPropertiesSet() throws Exception {Map<String, PayHandler> beansOfTypeMap = applicationContext.getBeansOfType(PayHandler.class);
        if (beansOfTypeMap == null || beansOfTypeMap.size() == 0) {return;}
        List<PayHandler> handlers = beansOfTypeMap.values().stream().collect(Collectors.toList());
        for (int i = 0; i < handlers.size(); i++) {PayHandler payHandler = handlers.get(i);
            if (i != handlers.size() - 1) {payHandler.setNext(handlers.get(i + 1));
            }
        }
        header = handlers.get(0);
    }
}
复制代码

这段代码的要害是每个 PayHandler 的子类,都定义了下一个须要执行的 PayHandler 子类,形成一个链式调用,通过 PayHandlerChain 把这种链式构造组装起来。

6、其余的打消 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.spring 中的判断

对于参数的异样,越早被发现越好,在 spring 中提供了 Assert 用来帮忙咱们检测参数是否无效。

public void save(Integer code,String name) {if(code == null) {throw new Exception("code 不能为空");     
     } else {if(name == null) {throw new Exception("name 不能为空");     
         } else {System.out.println("doSave");
         }
     }
 }
复制代码

如果参数十分多的话,if…else 语句会很长,这时如果改成应用 Assert 类判断,代码会简化很多:

public String save2(Integer code,String name) {Assert.notNull(code,"code 不能为空"); 
     Assert.notNull(name,"name 不能为空"); 
     System.out.println("doSave");
 }

参考:《2020 最新 Java 根底精讲视频教程和学习路线!》

链接:https://juejin.cn/post/691387…

退出移动版