Too much if-else in your code can greatly affect readability and maintainability.
First, readability. Needless to say, too much if-else code and nesting makes it hard for the reader to understand what it means. Especially code that doesn’t have comments.
The second is maintainability, because there are a lot of if-else, when you want to add a branch, it is very difficult to add, and it is very easy to affect other branches.
The author once saw a core payment application, which supports online payment functions of many businesses. However, each business has a lot of customization requirements, so there are a lot of if-else in many core codes.
Every time a new business needs customization, it puts its own if at the top of the entire method to ensure that its logic works. This approach, the consequences can be imagined.
In fact, there are ways to eliminate if-else, one of the most typical and widely used is to eliminate if-else completely in code with the help of the policy pattern and the factory pattern.
This article, combining these two design patterns, shows you how to eliminate if-else and how to combine them with the Spring framework so that you can immediately apply them to your own projects.
This article covers a bit of code, but the authors try to keep things simple by using generic examples and pseudocode.
Suppose we want to build a takeout platform with the following requirements:
1. In order to promote sales, a store on the takeaway platform has set up a variety of member discounts, including 20% discount for super members, 90% discount for ordinary members and no discount for ordinary users.
2. It is hoped that when users pay, they can know which discount strategy users meet according to their membership level, and then discount and calculate the amount payable.
3. With the development of business, new requirements require exclusive members to enjoy the discount only when the shop order amount is more than 30 yuan.
4, then, there is another abnormal demand, if the user’s super membership has expired, and the expiration time is within a week, then the user’s single order according to the super member discount, and strong reminder at the cashier, guide the user to open the membership again, and the discount is only once.
So, we can see the following pseudocode:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if(Users are exclusive members) {if(Order amount > 30 yuan) {returen 30% off the price; }}if(User is super member) {return20% off the price; }if(The user is a regular member) {if(The super member of this user has just expired and has not used the temporary discount.){The number of times of using the temporary discount is updated (); Returen 20% off; }return10% off the price; }returnThe original price. }Copy the code
Above, is a price calculation logic for this demand, the use of pseudo-code are so complex, if it is really written code, the complexity can be imagined.
There is a lot of if-else in this code, and there is a lot of if-else nesting, which is very readable and very maintainable.
So how can it be improved?
Next, we tried to introduce policy patterns to improve the maintainability and readability of the code.
First, define an interface:
/** * @author mhcoding */ public interface UserPayService {/** * public BigDecimal quote(BigDecimal) orderPrice); }Copy the code
Next, define a few policy classes:
/** * @author mhcoding */ public class ParticularlyVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) {if(consumption amount > 30 yuan) {return30% off the price; } } } public class SuperVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) {return20% off the price; } } public class VipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) {if(The super member of this user has just expired and has not used the temporary discount.){The number of times of using the temporary discount is updated (); Returen 20% off; }return10% off the price; }}Copy the code
After introducing the strategy, we can calculate the price as follows:
/**
* @author mhcoding
*/
public class Test {
public static void main(String[] args) {
UserPayService strategy = new VipPayService();
BigDecimal quote = strategy.quote(300);
System.out.println("The final price of goods for ordinary members is:" + quote.doubleValue());
strategy = new SuperVipPayService();
quote = strategy.quote(300);
System.out.println("The final price of super member goods is:"+ quote.doubleValue()); }}Copy the code
Above, is an example, can be in the code to create a different member of the strategy class, and then execute the corresponding calculation of the price method. For this example, as well as for strategic patterns, you can find out in how to Explain Strategic Patterns to Your Girlfriend. Study in one text.
However, for real use in code, such as in a Web project, the Demo above simply cannot be used directly.
First of all, in the Web project, the policy classes we created above are hosted by Spring and we don’t have to create new instances ourselves.
Secondly, in web projects, if you really want to calculate the price, it is also necessary to know the user’s membership level in advance, for example, find out the membership level from the database, and then obtain different policy classes according to the level to execute the price calculation method.
So, for a real price calculation in a Web project, the pseudocode would look like this:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
ifExclusive member) (vipType = = {/ / pseudo code: obtain super membership policy objects from the Spring UserPayService strategy = Spring. The getBean (ParticularlyVipPayService. Class);return strategy.quote(orderPrice);
}
if(vipType == Super member) {UserPayService Strategy = Spring.getBean(supervippayService.class);return strategy.quote(orderPrice);
}
if(vipType == ordinary member) {UserPayService Strategy = Spring.getBean(vippayService.class);return strategy.quote(orderPrice);
}
returnThe original price. }Copy the code
With the above code, the maintainability and readability of the code seems to be improved, but it doesn’t seem to reduce if-else.
In fact, in the previous video, How Do I Explain Strategic Patterns to My Girlfriend? In this article, we describe many advantages of the strategy pattern. However, there is a major drawback to the use of the policy pattern:
The client must know all the policy classes and decide which one to use. This means that the client must understand the differences between these algorithms in order to choose the right algorithm class at the right time.
In other words, although there is no if-else in the calculation of the price, there will inevitably be some if-else in the selection of the specific strategy.
In addition, in the above pseudo-code, we are implemented by pseudo-code to obtain the member policy object from Spring, so how should the code get the corresponding Bean?
Let’s look at how Spring and the factory pattern can solve these problems.
To get the policy classes for UserPayService from Spring, we create a factory class:
/**
* @author mhcoding
*/
public class UserPayServiceStrategyFactory {
private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();
public static UserPayService getByUserType(String type) {return services.get(type);
}
public static void register(String userType,UserPayService userPayService){
Assert.notNull(userType,"userType can't be null"); services.put(userType,userPayService); }}Copy the code
This UserPayServiceStrategyFactory defines a Map, strategy is used to store all the instances of the class, and provide a getByUserType method, can be directly obtained according to the type of the corresponding instance of the class. There’s also a register method, which I’ll talk about later.
With this factory class, the code to calculate the price can be greatly optimized:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType);
return strategy.quote(orderPrice);
}Copy the code
In this code, if-else is no longer needed. After getting the VIP type of the user, call the factory getByUserType method directly.
With policy + factory, our code has been greatly optimized, greatly improving readability and maintainability.
, however, it remains a problem, that is UserPayServiceStrategyFactory strategy is used to store all the instances of the class of the Map is initialized? What about instance objects of various policies such as Jose?
Remember we previously defined UserPayServiceStrategyFactory provides the register method? It is used to register the policy service.
Next, we’ll find a way to call the Register method and register the beans that Spring created through IOC.
We can use the InitializingBean interface provided by Spring to provide processing methods for Bean properties after initialization. This interface includes only afterPropertiesSet methods. Any classes that inherit this interface will execute this method after the Bean properties are initialized.
So, we can modify the previous each strategy class slightly:
/**
* @author mhcoding
*/
@Service
public class ParticularlyVipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(consumption amount > 30 yuan) {return30% off the price; } } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("ParticularlyVip",this);
}
}
@Service
public class SuperVipPayService implements UserPayService ,InitializingBean{
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return20% off the price; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("SuperVip",this);
}
}
@Service
public class VipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(The super member of this user has just expired and has not used the temporary discount.){The number of times of using the temporary discount is updated (); Returen 20% off; }return10% off the price; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("Vip",this); }}Copy the code
Only need every strategy service implementation class implements InitializingBean interface, and realize its afterPropertiesSet method, this method invokes the UserPayServiceStrategyFactory. Register.
Initialization so, in the Spring, when creating VipPayService, SuperVipPayService and ParticularlyVipPayService in Bean properties after the initialization, Put the Bean registered in UserPayServiceStrategyFactory.
The above code, in fact, there are some repetitive code, which can also be introduced to further simplify the template method pattern, here will not expand.
Also, UserPayServiceStrategyFactory. Register call, the first parameter to pass a string, actually also can optimize away here. Such as using enumerations, or a custom getUserType method for each policy class, implemented separately.
In this article, we improved the readability and maintainability of our code by using the policy pattern, the factory pattern, and Spring’s InitializingBean, eliminating a bunch of if-else completely.
This practice, you can immediately try, this practice, is our daily development is often used, and there are many derivative uses, are also very easy to use. I’ll introduce you later when I get the chance.
In fact, if the reader is familiar with the policy pattern and the factory pattern, the article does not use the policy pattern and the factory pattern strictly.
First of all, the important role of Context in the policy pattern is missing. Without Context, composition is not used, but factories are used instead.
In addition, here below the UserPayServiceStrategyFactory is just maintains a Map, and provides the register and the get method, and the factory pattern is to help create objects, there is no use.
So the reader doesn’t have to wrestle with whether the strategy versus factory model is really used. In addition, the so-called GOF 23 design patterns are simple code examples from any book or blog, but most of our daily development is based on frameworks such as Spring, which cannot be directly used.
Therefore, for the study of design patterns, it is important to learn the idea, not the code implementation!!
If readers are interested, more design patterns and best practices for using frameworks like Spring can follow. Hopefully, through articles like this, you can actually use design patterns in your code.