9 elimination if Else's tricks help you write more elegant code

Posted by Duke555 on Sat, 25 Dec 2021 23:56:32 +0100

1, Smelly and long if else

First look at the following code.

publicinterface IPay {  
    void pay();  
}  

@Service
publicclass AliaPay implements IPay {  
     @Override
     public void pay() {  
        System.out.println("===Launch Alipay payment===");  
     }  
}  

@Service
publicclass WeixinPay implements IPay {  
     @Override
     public void pay() {  
         System.out.println("===Initiate wechat payment===");  
     }  
}  
  
@Service
publicclass JingDongPay implements IPay {  
     @Override
     public void pay() {  
        System.out.println("===Initiate JD payment===");  
     }  
}  

@Service
publicclass 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("Payment method not found");  
         }  
     }  
}

The toPay method of PayService class is mainly used to initiate payment. According to different code s, it is decided to call the pay method of different payment classes (such as aliaPay) for payment.

What's wrong with this code? Maybe that's what some people do.

Imagine that if there are more and more payment methods, such as Baidu payment, meituan payment, UnionPay payment, etc., you need to change the code of toPay method and add a new else If judgment, more judgment will lead to more and more logic?

Obviously, it violates the six principles of design mode: opening and closing principle and {single responsibility principle.

Opening and closing principle: open to extension and close to modification. That is to say, add new functions and change existing code as little as possible.

Single responsibility principle: as the name suggests, the logic is required to be as single as possible, not too complex and easy to reuse.

What can be done to solve this problem?

2, Eliminate if Else's clever plan

1. Using annotations

The reason why code is used to judge which payment class to use in the code is because code and payment class do not have a binding relationship. If the binding relationship exists, you can not judge.

Let's define an annotation first.

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public@interface PayCode {  

     String value();    
     String name();  
}

Add this annotation to all payment classes

@PayCode(value = "alia", name = "Alipay payment")  
@Service
publicclass AliaPay implements IPay {  

     @Override
     public void pay() {  
         System.out.println("===Launch Alipay payment===");  
     }  
}  

 
@PayCode(value = "weixin", name = "Wechat payment")  
@Service
publicclass WeixinPay implements IPay {  
 
     @Override
     public void pay() {  
         System.out.println("===Initiate wechat payment===");  
     }  
} 

 
@PayCode(value = "jingdong", name = "JD payment")  
@Service
publicclass JingDongPay implements IPay {  
 
     @Override
     public void pay() {  
        System.out.println("===Initiate JD payment===");  
     }  
}

Then add the most critical classes:

@Service
publicclass PayService2 implements ApplicationListener<ContextRefreshedEvent> {  
 
     privatestatic 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();  
     }  
}

The PayService2 class implements the ApplicationListener interface so that you can get an instance of ApplicationContext in the onApplicationEvent method. We then get the class annotated with PayCode and put it into a map. The key in the map is the value defined in the PayCode annotation, which is consistent with the code parameter. Value is an instance of the payment class.

In this way, you can get the payment class instance directly through code every time instead of if Else judged. If you want to add a new payment method, just mark the PayCode annotation on the payment class and define a new code.

Note: Codes in this way can have no business meaning and can be pure numbers, only without repetition.

2. Dynamic splice name

This method is mainly used in scenarios where code has business meaning.

@Service
publicclass PayService3 implements ApplicationContextAware {   
     private ApplicationContext applicationContext;  
     privatestaticfinal 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;  
     }  
}

We can see that the name of payment bean is spliced by code and suffix, such as aliaPay, weixinPay and jingDongPay. This requires special attention when naming the payment class. The preceding paragraph should be consistent with the code. The instance of the called payment class is obtained directly from the ApplicationContext instance. By default, the bean is a singleton and placed in a map in memory, so there will be no performance problems.

In particular, this method implements the ApplicationContextAware interface, which is different from the ApplicationListener interface above. It wants to tell you that there are more than one methods to obtain ApplicationContext instances.

3. Template method judgment

Of course, in addition to the two methods introduced above, the source code implementation of spring also tells us another idea to solve if Else problem.

Let's take a look at some of the source code of spring AOP and the wrap method of defaultadvisor adapter registry

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);  
 }

Focus on the supportAdvice method. Three classes implement this method. Let's take a random class and have a look

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);  
     }   
}

The supportsadadvice method of this class is very simple. It just judges whether the type of advice is AfterReturningAdvice.

We should be inspired by what we see here.

In fact, we can do this by defining an interface or abstract class. There is a support method to judge whether the code passed by the parameter can be processed by ourselves. If it can be processed, go to the payment logic.

publicinterface IPay {  
     boolean support(String code);   
     void pay();  
}  

@Service
publicclass AliaPay implements IPay {   
     @Override
     public boolean support(String code) {  
        return"alia".equals(code);  
     }  
 
     @Override
     public void pay() {  
        System.out.println("===Launch Alipay payment===");  
     }  
}  
 
@Service
publicclass WeixinPay implements IPay {  
 
     @Override
     public boolean support(String code) {  
        return"weixin".equals(code);  
     }  
 
     @Override
     public void pay() {  
        System.out.println("===Initiate wechat payment===");  
     }  
}  

@Service
publicclass JingDongPay implements IPay {  
     @Override
     public boolean support(String code) {  
        return"jingdong".equals(code);  
     }  
 
     @Override
     public void pay() {  
        System.out.println("===Initiate JD payment===");  
     }  
}

Each payment class has a support method to judge whether the passed code is equal to its own definition.

@Service
publicclass 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();  
             }  
         }  
     }  
}

In this code, first initialize the payment class instance that implements the IPay interface into a list set, and return to loop through the list set when calling the payment interface. If the code is the same as that defined by yourself, call the pay method of the current payment class instance.

4. Strategy + factory mode

This method is also used in scenarios where code has business implications.

  • The policy pattern defines a set of algorithms, encapsulates them one by one, and makes them replaceable.

  • Factory mode is used to encapsulate and manage the creation of objects. It is a creation mode.

publicinterface IPay {
    void pay();
}

@Service
publicclass AliaPay implements IPay {

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


    @Override
    public void pay() {
        System.out.println("===Launch Alipay payment===");
    }

}

@Service
publicclass WeixinPay implements IPay {

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

    @Override
    public void pay() {
        System.out.println("===Initiate wechat payment===");
    }
}

@Service
publicclass JingDongPay implements IPay {

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

    @Override
    public void pay() {
        System.out.println("===Initiate JD payment===");
    }
}

publicclass PayStrategyFactory {

    privatestatic 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
publicclass PayService3 {

    public void toPay(String code) {
        PayStrategyFactory.get(code).pay();
    }
}

The key to this code is the PayStrategyFactory class, which is a policy factory. It defines a global map, registers the current instance in all IPay implementation classes to the map, and then obtains the payment class instance from the map through the PayStrategyFactory class according to the code.

5. Responsibility chain model

This method is used to eliminate if Else is very effective.

  • Responsibility chain mode: combine the processing objects of the request like a long chain to form an object chain. The request does not know which object is executing the request, which decouples the request from the processing object.

The commonly used filter and spring aop use the responsibility chain mode. Here I have slightly improved it. The specific code is as follows:

publicabstractclass PayHandler {

    @Getter
    @Setter
    protected PayHandler next;

    public abstract void pay(String pay);

}

@Service
publicclass AliaPayHandler extends PayHandler {


    @Override
    public void pay(String code) {
        if ("alia".equals(code)) {
            System.out.println("===Launch Alipay payment===");
        } else {
            getNext().pay(code);
        }
    }

}

@Service
publicclass WeixinPayHandler extends PayHandler {

    @Override
    public void pay(String code) {
        if ("weixin".equals(code)) {
            System.out.println("===Initiate wechat payment===");
        } else {
            getNext().pay(code);
        }
    }
}

@Service
publicclass JingDongPayHandler extends PayHandler {


    @Override
    public void pay(String code) {
        if ("jingdong".equals(code)) {
            System.out.println("===Initiate JD payment===");
        } else {
            getNext().pay(code);
        }
    }
}

@Service
publicclass 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);
    }
}

The key of this code is that each subclass of PayHandler defines the next subclass of PayHandler to be executed, forming a chain call, and assembling this chain structure through PayHandlerChain.

6. Other elimination if Else method

Of course, if is used in actual project development There are many scenarios judged by else. The above is just a few of them. Here are other common scenarios.

1. Return different strings according to different numbers

public String getMessage(int code) {  
     if (code == 1) {  
        return"success";  
     } elseif (code == -1) {  
        return"fail";  
     } elseif (code == -2) {  
        return"Network Timeout ";  
     } elseif (code == -3) {  
        return"Parameter error";  
     }  
     thrownew RuntimeException("code error");  
}

In fact, this judgment is not necessary. It can be done with an enumeration.

publicenum MessageEnum {  
     SUCCESS(1, "success"),  
     FAIL(-1, "fail"),  
     TIME_OUT(-2, "Network Timeout "),  
     PARAM_ERROR(-3, "Parameter error");  

     privateint code;  
     private String message;  

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

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

Adjust the calling method slightly

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

2. Judgment in set

The getMessageEnum method in the above enumeration MessageEnum may be written like this if the java8 syntax is not used

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

For filtering data in a collection or finding methods, Java 8 has a simpler way to eliminate if Else judgment.

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

3. Simple judgment

In fact, some simple if Else does not need to be written at all. It can be replaced by the ternary operator, for example:

public String getMessage2(int code) {  
     if(code == 1) {  
        return"success";  
     }  
     return"fail";  
}

Change to a trinocular operator:

public String getMessage2(int code) {  
    return code == 1 ? "success" : "fail";  
}

4. Judgment in spring

For parameter exceptions, the sooner they are found, the better. Assert is provided in spring to help us detect whether the parameters are valid.

public void save(Integer code,String name) {  
     if(code == null) {
       throw Exception("code Cannot be empty");     
     } else {
         if(name == null) {
             throw Exception("name Cannot be empty");     
         } else {
             System.out.println("doSave");
         }
     }
 }

If there are many parameters, if The else statement will be very long. At this time, if you use the Assert class to judge, the code will be much simplified:

public String save2(Integer code,String name) {      
     Assert.notNull(code,"code Cannot be empty"); 
     Assert.notNull(name,"name Cannot be empty"); 
     System.out.println("doSave");
 }

Topics: Java