preface

[Unlock new pose] Brother dei, your code needs to be optimized. As mentioned in the previous article, simple if-else can be optimized using the guard statement. But in real development, instead of a simple if-else structure, we often inadvertently write code like this:

-------------------- ideallyif-else  --------------------
public void today(a) {
    if (isWeekend()) {
        System.out.println("Play the game");
    } else {
        System.out.println("Work!"); }} -------------------- in realityif-else  --------------------

if (money >= 1000) {
    if (type == UserType.SILVER_VIP.getCode()) {

        System.out.println("Silver member discount 50 yuan");
        result = money - 50;
    } else if (type == UserType.GOLD_VIP.getCode()) {

        System.out.println("20% off for gold members.");
        result = money * 0.8;
    } else if (type == UserType.PLATINUM_VIP.getCode()) {

        System.out.println("$50 off for platinum members and 30% off.");
        result = (money - 50) * 0.7;
    } else {
        System.out.println("No discount for regular members."); result = money; }}//省略 n 个 if-else ......

Copy the code

It’s no exaggeration to say that we’ve all written code like this, and recalling our fear of being ruled by if-else, we’ve often done nothing, or even nothing.

Let me share my thoughts on the “elegant handling” of complex if-else statements I encountered in development. If there is anything wrong, welcome to exchange and study together.

demand

Suppose there is a requirement:

In an e-commerce system, when users consume more than 1000 amounts, they can enjoy discounts according to their VIP levels.

Calculate the final cost of the user according to the VIP level of the user.

  • There is no discount for regular members
  • Silver member discount 50 yuan
  • 20% off for gold members
  • 50 yuan off for platinum members and 30% off

coded

private static double getResult(long money, int type) {

    double result = money;

    if (money >= 1000) {
        if (type == UserType.SILVER_VIP.getCode()) {

            System.out.println("Silver member discount 50 yuan");
            result = money - 50;
        } else if (type == UserType.GOLD_VIP.getCode()) {

            System.out.println("20% off for gold members.");
            result = money * 0.8;
        } else if (type == UserType.PLATINUM_VIP.getCode()) {

            System.out.println("$50 off for platinum members and 30% off.");
            result = (money - 50) * 0.7;
        } else {
            System.out.println("No discount for regular members."); result = money; }}return result;
}
Copy the code

For the sake of demonstration, I’ve done a simple implementation in the code, but in reality if-else does complicated logic accounting. Functionally, it is basically complete, but for me, a code cleanliness freak, the quality of the code is not easy to look at. Let’s start by optimizing the first version of our code.

thinking

The first thing your smart friend might think is, isn’t this a typical strategy pattern?

You’re a real smart cookie. Let’s try to optimize the code with the strategy mode first.

The strategy pattern

What are strategic patterns?

Some friends may not be clear, what is the mode of strategy. The strategy pattern is to define a series of algorithms, encapsulate them one by one, and make them interchangeable.

For example, the above needs, there are rebates, discounts, and so on. These algorithms are a strategy in themselves. And these algorithms can be replaced with each other. For example, today I want to give a 50 percent discount to silver members, and tomorrow I can replace it with a 10 percent discount to silver members.

So much said, not as real as coding.

coding

public interface Strategy {
    
    // Charging method
    double compute(long money);
}

// General membership strategy
public class OrdinaryStrategy implements Strategy {

    @Override
    public double compute(long money) {
        System.out.println("No discount for regular members.");
        returnmoney; }}// Silver Membership strategy
public class SilverStrategy implements Strategy {

    @Override
    public double compute(long money) {

        System.out.println("Silver member discount 50 yuan");
        return money - 50; }}// Gold membership strategy
public class GoldStrategy implements Strategy{

    @Override
    public double compute(long money) {
        System.out.println("20% off for gold members.");
        return money * 0.8; }}// Platinum membership strategy
public class PlatinumStrategy implements Strategy {
    @Override
    public double compute(long money) {
        System.out.println("$50 off for platinum members and 30% off.");
        return (money - 50) * 0.7; }}Copy the code

We define a Strategy interface and define four subclasses to implement the interface. The corresponding compute method implements the accounting logic of its own policy.

private static double getResult(long money, int type) {

    double result = money;

    if (money >= 1000) {
        if (type == UserType.SILVER_VIP.getCode()) {

            result = new SilverStrategy().compute(money);
        } else if (type == UserType.GOLD_VIP.getCode()) {

            result = new GoldStrategy().compute(money);
        } else if (type == UserType.PLATINUM_VIP.getCode()) {

            result = new PlatinumStrategy().compute(money);
        } else {
            result = newOrdinaryStrategy().compute(money); }}return result;
}
Copy the code

Then the corresponding getResult method is replaced with the corresponding USER VIP policy based on type. There are repeated calls to compute on the code, which we can try to optimize further.

private static double getResult(long money, int type) {

    if (money < 1000) {
        return money;
    }

    Strategy strategy;

    if (type == UserType.SILVER_VIP.getCode()) {
        strategy = new SilverStrategy();
    } else if (type == UserType.GOLD_VIP.getCode()) {
        strategy = new GoldStrategy();
    } else if (type == UserType.PLATINUM_VIP.getCode()) {
        strategy = new PlatinumStrategy();
    } else {
        strategy = new OrdinaryStrategy();
    }

    return strategy.compute(money);
}
Copy the code

Remember the guard statement I talked about in the first post? Here we return money < 1000 ahead of time. Focusing more on Max 1000 logic also reduces unnecessary indentation.

ponder

There was a time when I thought the strategic model was just that. I thought the code had been optimized to this point.

But there is another horrible thing, if-else still exists 🙂

I tried to read a number of books on how to eliminate if-else in policy mode

Most of the approach in the book is to use the simple factory + policy pattern. Switch if-else to switch to create a factory method.

But this is nowhere near as good as I want it to be, knock down if-else

Until one night, when I shared a Java8 tip in the group, it opened up a whole new world.

Factory + Strategy

public interface Strategy {

    double compute(long money);

    / / return type
    int getType(a);
}


public class OrdinaryStrategy implements Strategy {

    @Override
    public double compute(long money) {
        System.out.println("No discount for regular members.");
        return money;
    }

    // Add type returns
    @Override
    public int getType(a) {
        returnUserType.SILVER_VIP.getCode(); }}public class SilverStrategy implements Strategy {

    @Override
    public double compute(long money) {

        System.out.println("Silver member discount 50 yuan");
        return money - 50;
    }

    / / return type
    @Override
    public int getType(a) {
        returnUserType.SILVER_VIP.getCode(); }}... Omit remaining StrategyCopy the code

We will first add a getType method to the Strategy that identifies the type value of the policy. The code is relatively simple and will not be covered here

public class StrategyFactory {

    private Map<Integer, Strategy> map;

    public StrategyFactory(a) {

        List<Strategy> strategies = new ArrayList<>();

        strategies.add(new OrdinaryStrategy());
        strategies.add(new SilverStrategy());
        strategies.add(new GoldStrategy());
        strategies.add(new PlatinumStrategy());
        strategies.add(new PlatinumStrategy());

        // Look here look here look here!
        map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
        
        /* map = new HashMap<>(); for (Strategy strategy : strategies) { map.put(strategy.getType(), strategy); } * /
    }

    public static class Holder {
        public static StrategyFactory instance = new StrategyFactory();
    }

    public static StrategyFactory getInstance(a) {
        return Holder.instance;
    }

    public Strategy get(Integer type) {
        returnmap.get(type); }}Copy the code

Static inner class singleton, a kind of singleton pattern implementation, is not the focus of this article, if you do not understand, you can Google

Let’s start creating a StrategyFactory factory class. StrategyFactory is a static inner class singleton. When constructing the method, initialize the Strategy required and transform the list into a map. Here the transformation is the “soul”.

toMap

Let’s start by looking at a few tricks in Java8 syntax.

Normally, we iterate through the List and manually put it into the Map.

--------------  before -----------------

map = new HashMap<>();
for (Strategy strategy : strategies) {
    map.put(strategy.getType(), strategy);
}

--------------  after Java8 -----------------

map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
Copy the code

ToMap the first argument is a Function, corresponding to the key in the Map, and the second argument is also a Function, strategy -> strategy. The strategy on the left is to traverse each strategy in the strategies. Strategy on the right is the value corresponding to Map.

If you are not familiar with Java8 syntax, you are strongly advised to read “Java8 In Action”, which introduces Lambda expressions, Stream and other syntax in detail.

The effect

private static double getResult(long money, int type) {

    if (money < 1000) {
        return money;
    }

    Strategy strategy = StrategyFactory.getInstance().get(type);
    
    if (strategy == null) {throw new IllegalArgumentException("please input right type");
    }

    return strategy.compute(money);
}
Copy the code

At this point, through a factory class, we can get the Strategy based on the type passed in when we call getResult ()

No more dreaded if-else statements.

End scatter flower scatter flower:)

subsequent

For further code optimization, if you are a Java project, you can try to use custom annotations to annotate the Strategy implementation class.

This simplifies the need to add a Stratey policy to the factory class List.

The last

The above is my complex if-else statement in the development of “elegant processing” ideas, if there is something wrong, welcome to exchange and learn together.