Highlights of dazzling skills Eliminate If Else under SpringBoot to make your code more eye-catching

Article directory

    • background
    • the case
      • first stage germination
      • Stage Two: Carving on Shit
      • Phase 3 Strategy + Factory Pattern Refactoring
      • Phase 4 Optimization
    • Summarize

Background

Hello everyone, I am big cousin laker. Today, I want to share with you an article about How to use strategy pattern and factory pattern to eliminate If Else coupling problem. This approach can make your code more beautiful, concise and easy to maintain.

  • In the team, we all hope to be a person with relatively good coding ability and give the leader an eye-catching code.

  • During the interview, I also want to tell the interviewer the highlights of the project and highlight my own value.

  • During development, no one wants to carve flowers on shit. When encountering the problem that the code is difficult to maintain, a small business change requires a large amount of code modification, and it is difficult to solve the problem even if you work overtime.

Old cousins, therefore, today I share this article, hoping to help you solve these problems, so that you can be more efficient when writing code, work less overtime, and enjoy life more.

Case

In the user payment module, different payment methods have different implementation methods, such as WeChat payment, Alipay payment, UnionPay payment< /strong>, etc., the user can freely choose the payment method to pay. In code implementation, a large number of If Else statements are often used to determine the payment method selected by the user, and different processing logics are invoked according to different payment methods.

The first stage of germination

At the beginning of the project, we only connected Alipay and WeChat payment. At this time, the payment module code is as follows:

@Service
public class PaymentService {<!-- -->

    @Autowired
    private AliPayService aliPayService;
    @Autowired
    private WeChatPayService weChatPayService;

    public String pay(String payType, PayRequest request) {<!-- -->
        if ("aliPay".equals(payType)) {<!-- -->
            return aliPayService.pay(request);
        } else if ("weChatPay".equals(payType)) {<!-- -->
            return weChatPayService.pay(request);
        }
        // unsupported payment method, throw an exception
        throw new RuntimeException("Unsupported pay type: " + payType);
    }
}

At this time, this way of writing is no problem, and it is also in line with the current state.

Second stage Carving on shit

Later, as the business expanded well and the number of users increased, we needed to support other payment methods, such as UnionPay payment and Huifu payment, etc. The core backbone code was modified as follows:

@Service
public class PaymentService {<!-- -->

    @Autowired
    private AliPayService aliPayService;
    @Autowired
    private WeChatPayService weChatPayService;
    @Autowired
    private UnionPayService unionPayService;
    @Autowired
    private AdaPayService adaPayService;

    public String pay(String payType, PayRequest request) {<!-- -->
        if ("aliPay".equals(payType)) {<!-- -->
            return aliPayService.pay(request);
        } else if ("weChatPay".equals(payType)) {<!-- -->
            return weChatPayService.pay(request);
            //  Change the core backbone code here
        } else if ("unionPay".equals(payType)) {<!-- -->
            return unionPayService.pay(request);
        } else if ("adaPay".equals(payType)) {<!-- -->
            return adaPayService.pay(request);
        }
           //  Change the core backbone code here
        // unsupported payment method, throw an exception
        throw new RuntimeException("Unsupported pay type: " + payType);
    }
}

At this time, if you do not refactor but continue to write code in this way, there will be hidden dangers. At this time, we will start to “carve flowers on shit”.

This implementation method will lead to poor code maintainability, high code complexity, and also increase the difficulty of code modification.

Important! focus! focus! What’s wrong with writing this way?

1. It is foreseeable that as the business volume grows, we will definitely add other payment methods, so we need to directly modify the main code, and we advocate that the code should be closed to modification strong>.

2. The payment module is highly coupled with the specific payment method. For example, what parameters need to be added to WeChat payment, we still need to directly modify the main code, which leads to changes in the interface that will directly affect the organization of the code, making the code maintainability is reduced.

The third stage strategy + factory pattern reconstruction

We want to refactor to achieve no modification of the main code when expanding now and in the future, here we will use strategy pattern + factory pattern strong> to solve this problem and make the code more readable and extensible.

Step 1 First, we define a PayStrategy interface, and define a pay() method in the interface, which will receive a PayRequest object and return a string.

public interface PayStrategy {<!-- -->
    String pay(PayRequest request);
}

Step 2 Next, we need to create an implementation class for each specific policy implementation.

@Component("aliPay")
public class AliPayStrategy implements PayStrategy {
   @Override
    public String pay(PayRequest request) {
        // Specific payment logic
        return "Using Alipay to pay successfully";
    }
}
@Component("weChatPay")
public class WeChatPayStrategy implements PayStrategy {

    @Override
    public String pay(PayRequest request) {
        // Specific payment logic
        return "Using WeChat to pay successfully";
    }
}
@Component("unionPay")
public class UnionPayStrategy implements PayStrategy {
    @Override
    public String pay(PayRequest request) {
        // Specific payment logic
        return "Pay with UnionPay successfully";
    }
}
@Component("adaPay")
public class AdaPayStrategy implements PayStrategy {
    @Override
    public String pay(PayRequest request) {
        // Specific payment logic
        return "Using Huifu Tianxia to pay successfully";
    }
}

Step 3 We need to define a factory class to produce specific strategy implementation objects. Here we use Spring’s @Autowired annotation to automatically inject all classes that implement the PayStrategy interface, and then return the corresponding implementation class according to different payment methods:

@Component
public class PaymentFactory {<!-- -->

    @Autowired
    private Map<String, PayStrategy> payStrategyMap;

    public PayStrategy getPayStrategy(String payType) {<!-- -->
        PayStrategy payStrategy = payStrategyMap. get(payType);
        if (payStrategy == null) {<!-- -->
            throw new RuntimeException("Unsupported pay type: " + payType);
        }
        return payStrategy;
    }
}

In the PayFactory class, we use the @Autowired annotation to automatically inject all classes that implement the PayStrategy interface, so that we can dynamically New payment methods are added to the system without modifying the code of the factory class. Then, we define a getPayStrategy() method, which receives a payment method as a parameter, and then returns the corresponding implementation class according to the payment method.

Step 4 Finally, we modify the pay method in PaymentService, use the PayFactory class to obtain the specific policy implementation object and call its pay() method Make a payment.

@Service
public class PaymentService {<!-- -->

    @Autowired
    private PaymentFactory paymentFactory;

    public String pay(String payType, PayRequest request) {<!-- -->
        PayStrategy payStrategy = paymentFactory. getPayStrategy(payType);
        return payStrategy. pay(request);
    }
}

In the PaymentService class, we use the @Autowired annotation to automatically inject instances of the PayFactory class. Then, we define a pay() method, which receives a payment method and a PayRequest object as parameters, and then obtains the corresponding method through the PayFactory class The specific policy implementation object, and call its pay() method.

By using the strategy pattern and the factory pattern, we successfully eliminated the if-else statement in the code, making the code more beautiful, concise, and easy to maintain. Moreover, When we need to add a new payment method, just add a class that implements the PayStrategy interface strong>.

Taking the increase of Agricultural Bank card payment as an example, our modification point is only to add a class as follows.
Here we can see that when we have a new payment method added, you are adding a new class instead of modifying the original class.

@Component("NYBankPay")
public class NYBankPayStrategy implements PayStrategy {<!-- -->
    @Override
    public String pay(PayRequest request) {<!-- -->
        // Specific payment logic
        return "Using the Agricultural Bank to pay successfully";
    }
}

The fourth stage of optimization

In order to manage payment methods more conveniently, we can use the enumeration class PaymentTypeEnum, which contains all payment methods, such as: Alipay, WeChat payment, UnionPay payment, etc.

For example, we can define the PaymentTypeEnum enumeration class as follows:

public enum PaymentTypeEnum {<!-- -->
    ALIPAY("aliPay"),
    WECHAT_PAY("wechatPay"),
    UNION_PAY("unionPay");

    private String name;

    PaymentTypeEnum(String name) {<!-- -->
        this.name = name;
    }

    public static PaymentTypeEnum getPaymentType(String name){<!-- -->
        for (PaymentTypeEnum paymentTypeEnum : values()) {<!-- -->
            if (paymentTypeEnum.name.equals(name)) {<!-- -->
                return paymentTypeEnum;
            }
        }
        throw new RuntimeException("Unsupported pay type: " + name);
    }


    public String getName() {<!-- -->
        return name;
    }
}

Then, in our payment service, we can determine which payment method to use by obtaining the payment method enumeration in the payment request, for example:

@Service
public class PaymentService {<!-- -->

    @Autowired
    private PaymentFactory paymentFactory;
// Limited parameter PaymentTypeEnum.getPaymentType(payType)
    public String pay(PaymentTypeEnum payType, PayRequest request) {<!-- -->
        PayStrategy payStrategy = paymentFactory. getPayStrategy(payType);
        return payStrategy. pay(request);
    }
}

In this way, we can easily add, manage, and switch different payment methods, making our code more flexible and scalable.

Other related codes are modified as follows:

//Add an abstract strategy class to implement the specific payment enumeration type of each concrete implementation class
public abstract class AbstractPaymentStrategy implements PayStrategy{<!-- -->
    public abstract PaymentTypeEnum paymentType();
}
// Correspondingly modify the specific payment implementation class
@Component
public class AliPayStrategy extends AbstractPaymentStrategy {<!-- -->
   @Override
    public String pay(PayRequest request) {<!-- -->
        // Specific payment logic
        return "Using Alipay to pay successfully";
    }
    // Here it means that this implementation class is aliPay type payment
    @Override
    public PaymentTypeEnum paymentType() {<!-- -->
        return PaymentTypeEnum.ALIPAY;
    }
}

@Component
public class WeChatPayStrategy extends AbstractPaymentStrategy {<!-- -->

    @Override
    public String pay(PayRequest request) {<!-- -->
        // Specific payment logic
        return "Using WeChat to pay successfully";
    }

    @Override
    public PaymentTypeEnum paymentType() {<!-- -->
        return PaymentTypeEnum.WECHAT_PAY;
    }
}


@Component
public class PaymentFactory {<!-- -->

    private final Map<PaymentTypeEnum, PayStrategy> payStrategyMap = new HashMap<>();
// In this way to achieve factory registration
    @Autowired
    public PaymentFactory(List<AbstractPaymentStrategy> payStrategyList) {<!-- -->
        payStrategyList.forEach(payStrategy -> payStrategyMap.put(payStrategy.paymentType(), payStrategy));
    }

    public PayStrategy getPayStrategy(PaymentTypeEnum payType) {<!-- -->
        PayStrategy payStrategy = payStrategyMap. get(payType);
        if (payStrategy == null) {<!-- -->
            throw new RuntimeException("Unsupported pay type: " + payType);
        }
        return payStrategy;
    }
}

Summary

When using if-else or switch statements for complex conditional judgments, the code becomes difficult to read and maintain. At this time, using the strategy pattern and the factory pattern can effectively eliminate the conditional judgment statement, making the code more beautiful, concise, and easy to maintain.

When you encounter an if-else or switch statement when coding, you have to think about whether you can use strategy mode and factory mode to optimize it

Scenarios for using the strategy pattern and the factory pattern include selection algorithms, payment methods, preferential methods, calling different systems according to types, etc.

The advantages of the strategy pattern and the factory pattern include good scalability, compliance with the principle of opening and closing, compliance with the principle of single responsibility, good readability, easy maintenance, and avoiding multi-layer judgments.

However, the disadvantage is that when the number of strategies increases, the number of strategy classes will also increase, which is not friendly for novices to read code, and excessive use of design patterns is also very dangerous.

Note that there is no silver bullet There is no silver bullet Use design patterns to better solve business problems, not use design patterns for design patterns’ sake. Over-engineering is also undesirable.

syntaxbug.com © 2021 All Rights Reserved.