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