Deep parsing: how to replace the ifelse in the code

When we write code, we need to deal with different business logic according to different situations. The most commonly used ones are if and else. But if there are too many cases, there will be a lot of "if else", which is why in many legacy systems, a function may appear thousands of lines of code. Of course, you say that it can be implemented by extracting methods or classes. Each situation can be handled by a method or a corresponding class. But it just looks like the code is cleaner. There are still a large number of "if else". When there is new logic behind it, more "if else" will be added, which does not fundamentally solve the problem.

For example, for the implementation of SMS sending business, general companies will access multiple SMS providers, such as dreamnet, Xuanwu, alicloud and other SMS platforms (we call them SMS channels), which may need to change the SMS channels according to different SMS types or the stability of the SMS platform:
1. For example, Alibaba cloud has strict SMS control, and messages with marketing characters are not allowed to be sent, so marketing SMS needs to be sent through other SMS channels;
2. It is also possible that one SMS platform service is temporarily unavailable and needs to be switched to another SMS channel;
3. If some SMS platforms have preferential policies, they need to temporarily switch to the SMS channel to send SMS;
4....

code implementation

The above business scenario is simple: call the corresponding SMS platform interface for different SMS channels to send SMS.
Short message channels are generally configured in files or databases.

The code implementation is as follows (note that all the following codes cannot be run directly, only the sample code of the key logic part):

Bad code example

We have a SMS sending class: SmsSendService, which has a send method to send SMS
SmsSendService.java

public class SmsSendService{
    /**
     * @Param phoneNo cell-phone number
     * @Param content SMS content
     */
    public void send(String phoneNo,String content){
        //Read SMS channel from configuration
        String channelType=config.getChannelType();

        //If it is SMS channel A, call the api of channel A to send
        if(Objects.equals(channelType,"CHANNEL_A")){
            System.out.println("Via SMS channel A Send SMS");
        }
        //If it is SMS channel B, call the api of channel B to send
        else if(Objects.equals(channelType,"CHANNEL_B")){
            System.out.println("Via SMS channel B Send SMS");
        }
    }
}

If one day an SMS channel C is added, then an else if is added "

//... part of the code is omitted here

//Read SMS channel from configuration
String channelType=config.getChannelType();
//If it is SMS channel A, call the api of channel A to send
if(Objects.equals(channelType,"CHANNEL_A")){
    System.out.println("Via SMS channel A Send SMS");
}
//If it is SMS channel B, call the api of channel B to send
else if(Objects.equals(channelType,"CHANNEL_B")){
    System.out.println("Via SMS channel B Send SMS");
}
//ADD: if it is SMS channel C, call the api of channel C to send
else if(Objects.equals(channelType,"CHANNEL_C")){
    System.out.println("Via SMS channel C Send SMS");
}

//... part of the code is omitted here

What if we add other SMS channels? You write another "else if "?
Obviously, this approach is not desirable and does not conform to the "opening and closing principle" in SOLID principle, which is open to expansion and closed to change.
In this way, we need to modify the original code every time (without closing the changes), and constantly add "if else".

Next, let's optimize the code:

Optimization code 1

Define a SMS channel interface SmsChannelService, which is implemented by all SMS channel API s;
SMS channel interface SmsChannelService.java

public interface SmsChannelService{
    //Send SMS
    void send(String phoneNo,String content);
}

SMS channel A SmsChannelServiceImplA.java

public class SmsChannelServiceImplA implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel A Send SMS");
    }
}

SMS channel B SmsChannelServiceImplB.java

public class SmsChannelServiceImplB implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel B Send SMS");
    }
}

Initialize all SMS channel service s through factory class
SmsChannelFactory.java

public class SmsChannelFactory {
    private Map<String,SmsChannelService> serviceMap;

    //Initialize the factory and put all SMS channel services into the Map
    public SmsChannelFactory(){
        //The channel type is key, and the corresponding service class is value:
        serviceMap=new HashMap<String, SmsChannelService>(2);
        serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
        serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
    }

    //Obtain Service of corresponding channel according to SMS channel type
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

The interface of different SMS channels is invoked in the original SmsSendService.
The original SmsSendService class is optimized as follows

public class SmsSendService {

    private SmsChannelFactory smsChannelFactory;

    public SmsSendService(){
        smsChannelFactory=new SmsChannelFactory();
    }

    public void send(String phoneNo,String content){
        //Read SMS channel from configuration
        String channelType=config.getChannelType();
        //Obtain the service class corresponding to the channel type
        SmsChannelService channelService=smsChannelFactory.buildService(channelType);
        //Send SMS
        channelService.send(phoneNo,content);
    }

}

In this way, the SmsSendService class is very concise, killing "if else",
If I want to add a SMS channel C, I don't need to change the SmsSendService class again.
You only need to add a class SmsChannelServiceImplC to implement the SmsChannelService interface,
Then add a line of code to the factory class SmsChannelFactory to initialize SmsChannelServiceImplC.

The implementation of adding SMS channel C SmsChannelServiceImplC.java

public class SmsChannelServiceImplC implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel C Send SMS");
    }
}

Modify the factory class SmsChannelFactory.java

public class SmsChannelFactory {
    private Map<String,SmsChannelService> serviceMap;

    //Initialize serviceMap and put all SMS channel services into the Map
    public SmsChannelFactory(){
        //The channel type is key, and the corresponding service class is value:
        serviceMap=new HashMap<String, SmsChannelService>(3);
        serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
        serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
        //ADD a line of SmsChannelServiceImplC initialization code 
        serviceMap.put("CHANNEL_C",new SmsChannelServiceImplC());
    }

    //Build SMS channel Service according to channel type
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

"if else" is dead, but you still have to modify the original SmsChannelFactory class, which does not meet the "open close principle". Is there a better way?

We further optimize our code by using spring's dependency injection:

Optimization code 2

The SmsChannelService interface adds the getChannelType() method, which is a key step.

public interface SmsChannelService {
    //Send SMS
    void send(String phoneNo,String content);
    //Key: add getChannelType() method, which is implemented by subclass to identify channel type
    String getChannelType();
}

Subclass add the implementation of this method, and add @Service Annotations to enable the spring container to be managed
SmsChannelServiceImplA.java

@Service
public class SmsChannelServiceImplA implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel A Send SMS");
    }
    //Key: add getChannelType() implementation
    public String getChannelType() {
        return "CHANNEL_A";
    }
}

SmsChannelServiceImplB.java

@Service
public class SmsChannelServiceImplB implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel B Send SMS");
    }
    //Key: add getChannelType() implementation
    public String getChannelType() {
        return "CHANNEL_B";
    }
}

Modify the SmsChannelFactory class: this step is also critical.
SmsChannelFactory.java

@Service
public class SmsChannelFactory {

    private Map<String,SmsChannelService> serviceMap;

    /*Injection: inject all instances of classes that implement the SmsChannelService interface into the serviceList through the spring container*/
    @Autowired
    private List<SmsChannelService> serviceList;

    /*Initialize serviceMap through @ PostConstruct annotation after instantiation of SmsChannelFactory */
    @PostConstruct
    private void init(){
        if(CollectionUtils.isEmpty(serviceList)){
            return ;
        }
        serviceMap=new HashMap<String, SmsChannelService>(serviceList.size());
        //Convert serviceList to serviceMap
        for (SmsChannelService channelService : serviceList) {
            String channelType=channelService.getChannelType();
            //Repeat the verification to avoid the getChannelType() method of different implementation classes returning the same value.
            if(serviceMap.get(channelType)!=null){
                throw new RuntimeException("The same SMS channel can only have one implementation class");
            }
            /*The channel type is key, and the corresponding service class is value:
            Compared with the manual setting of channel A and channel B in optimization code 1,
            This way is more automatic. In the subsequent addition of "channel ﹣ C", there is no need to change the code here*/
            serviceMap.put(channelType,channelService);
        }
    }

    //Get the Service of the corresponding SMS channel according to the channel type
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

SmsSendService plus @Service Comments. Inject SmsChannelFactory through @ Autowired
SmsSendService.java

@Service
public class SmsSendService {

    @Autowired
    private SmsChannelFactory smsChannelFactory;

    public void send(String phoneNo,String content){
        //Read SMS channel type from configuration
        String channelType=config.getChannelType();
        //Build service class corresponding to channel type
        SmsChannelService channelService=smsChannelFactory.buildService(channelType);
        //Send SMS
        channelService.send(phoneNo,content);
    }

}

At this time, if you need to add a channel C, you really only need to add a SmsChannelServiceImplC. You don't need to change the original code any more and follow the "open close principle" completely.

SmsChannelServiceImplC.java

@Service
public class SmsChannelServiceImplC implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("Via SMS channel C Send SMS");
    }

    public String getChannelType() {
        return "CHANNEL_C";
    }
}

summary
Through the above optimization, the "if else" is removed, and the "smelly and long" like "toilet roll" code will no longer appear, and the "opening and closing principle" is fully followed.
spring is a good thing. It depends on how you use it.

Zhengzhou test tube baby hospital: http://yyk.39.net/hospital/fc964_detail.html

Tags: Mobile Java Spring

Posted on Sat, 16 May 2020 05:51:40 -0400 by mooshuligan