JAVA programming idea, get rid of the awkward if, the self registration strategy mode meets the opening and closing principle gracefully, 80% of people don't know

Keywords: Java

1. Example of strategy pattern prototype

To implement a tax calculation strategy, the tax calculation types include internal and external taxes, and new tax types may be added in the future. The initial design class structure is as follows:

 

 

class Duty
TaxStrategy Tax policy interface
InterTaxStrategy In price tax strategy, responsible for calculating in price tax
OuterTaxStrategy Extra price tax policy, responsible for calculating extra price tax
TaxType Tax type definition, currently there are only in price tax and out of price tax
TaxStrategyFactory Tax policy factory obtains different tax policies according to tax type to calculate tax

Also note here: no matter you are for Java high salary or hobbies, remember: project development experience is always the core. If you don't have the latest Java architecture practical video tutorial and the interview book of large factory, you can go to the small-scale Java architecture to learn. Skirt: seven umbrella bar zero, but no umbrella (Digital homophony) you can find it under conversion. There are many new Java architecture project tutorials in it. You can also communicate with the old driver for advice!  


2. Code

2.1. Tax policy code

public interface TaxStrategy {
    double calc(long amount);
}

class InterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount * taxRate;
    }
}

class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount / (1 + taxRate) * taxRate;
    }
}

// Definition of tax type
public enum TaxType {
    INTER, OUTER
}
Copy code

2.2. Tax policy factory implemented by if statement

// Tax strategy factory
public class TaxStrategyFactory {
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding a new tax type, you need to modify the code and increase the circle complexity
        if (taxType == TaxType.INTER) {
            return new InterTaxStrategy();
        } else if (taxType == TaxType.OUTER) {
            return new OuterTaxStrategy();
        } else {
            throw new Exception("The tax type is not supported.");
        }
    }
}
Copy code

It can be seen that if you use the if statement to obtain different tax policies, when you add new tax policies, you have to modify the existing code. When there are many tax calculation methods, they are not so good-looking, but also increase the circle complexity.

2.3. Use Map instead of if in the first optimized tax strategy factory

public class MapTaxStrategyFactory {
    // Storage tax policy
    static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>();

    // Register default tax policy
    static {
        registerTaxStrategy(TaxType.INTER, new InterTaxStrategy());
        registerTaxStrategy(TaxType.OUTER, new OuterTaxStrategy());
    }

    // Provide the tax registration policy interface. External users only need to call this interface to add a new tax policy without modifying the internal code of the policy factory
    public static void registerTaxStrategy(TaxType taxType, TaxStrategy taxStrategy) {
        taxStrategyMap.put(taxType, taxStrategy);
    }

    // Get the tax policy through map. When adding new tax policy, you don't need to modify the code. It is closed to modification, open to extension and follows the open close principle
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding a new tax type, you need to modify the code and increase the circle complexity
        if (taxStrategyMap.containsKey(taxType)) {
            return taxStrategyMap.get(taxType);
        } else {
            throw new Exception("The tax type is not supported.");
        }
    }
}
Copy code

It can be seen that after evolution, IF statements are not available, which reduces cycle complexity. After adding new policies, only the policy registration interface needs to be called, and the code for obtaining tax policies does not need to be modified.

2.4. Automatic registration of secondary optimization strategy

In the above implementation, to register a new tax policy, you must manually call the registration interface of MapTaxStrategyFactory. In this way, for each new tax policy added, you need to modify the existing code, or find an appropriate initialization call point to register the tax policy. How can it perfectly conform to the opening and closing principle, close the modification, and open the extension?

After further optimization, the class structure is as follows:

 

 

class Duty
TaxStrategy Tax policy interface, providing tax calculation interface, and registering to tax policy factory
InterTaxStrategy In price tax strategy, responsible for calculating in price tax
OuterTaxStrategy Extra price tax policy, responsible for calculating extra price tax
TaxType Tax type definition, currently there are only in price tax and out of price tax
AutoRegisterTaxStrategyFactory Tax policy factory obtains different tax policies to calculate tax according to tax type, and provides tax policy registration interface

Let me look at the changed code:

2.4.1. Tax strategy

public interface TaxStrategy {
    double calc(long amount);
    // New self registration interface
    void register();
}

class InterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount * taxRate;
    }

    @Override public void register() {
        // Register yourself in the policy factory
        AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.INTER, this);
    }
}

class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount / (1 + taxRate) * taxRate;
    }

    @Override public void register() {
        // Register yourself in the policy factory
        AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.OUTER, this);
    }
}
Copy code

2.4.2. Tax factory

import java.util.*;

import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;

public class AutoRegisterTaxStrategyFactory {
    // Storage tax policy
    static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>();

    static {
        // Registration tax strategy
        autoRegisterTaxStrategy();
    }

    // Get tax policy through map. When adding new tax policy, you don't need to modify the code. It is closed to modification, open to extension, and follows the open closed principle
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding a new tax type, you need to modify the code and increase the circle complexity
        if (taxStrategyMap.containsKey(taxType)) {
            return taxStrategyMap.get(taxType);
        } else {
            throw new Exception("The tax type is not supported.");
        }
    }

    // Provide the tax registration policy interface. External users only need to call this interface to add a new tax policy without modifying the internal code of the policy factory
    public static void registerTaxStrategy(TaxType taxType, TaxStrategy taxStrategy) {
        taxStrategyMap.put(taxType, taxStrategy);
    }

    // Automatic registration tax policy
    private static void autoRegisterTaxStrategy() {
        try {
            // Find all tax policy subclasses for registration through reflection
            Reflections reflections = new Reflections(new ConfigurationBuilder()
                    .setUrls(ClasspathHelper.forPackage(TaxStrategy.class.getPackage().getName()))
                    .setScanners(new SubTypesScanner()));
            Set<Class<? extends TaxStrategy>> taxStrategyClassSet = reflections.getSubTypesOf(TaxStrategy.class);

            if (taxStrategyClassSet != null) {
                for (Class<?> clazz: taxStrategyClassSet) {
                    TaxStrategy taxStrategy = (TaxStrategy)clazz.newInstance();
                    // Self registration method of calling tax policy
                    taxStrategy.register();
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            // Self defined exception handling
            e.printStackTrace();
        }
    }
}
Copy code

Note: the dependency that the reflection tool needs to add in the code is as follows

        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.12</version>
        </dependency>

        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
            <optional>true</optional>
        </dependency>
Copy code

2.4.3. Use

public class DecisionDemo {
    public static void main(String[] args) throws Exception {
        TaxStrategy taxStrategy = AutoRegisterTaxStrategyFactory.getTaxStrategy(TaxType.INTER);
        System.out.println(taxStrategy.calc(100));
    }
}
Copy code

At this point, when adding a new tax policy, you do not need to modify the existing tax policy factory code at all. Basically, the principle of opening and closing is perfect. The only thing that needs to be modified is the definition of tax type.

2.5. Three times optimization reduces coupling through annotation (suggested by user Gu Yuanyuan)

The basic idea is to use annotations to explain what type of tax is used in tax strategies, and to automatically register the tax strategy according to annotations in tax strategy factories, without calling the registration interface of tax policy factories in each tax policy. The class structure is as follows:

 

 

Let's look at the changing code.

2.5.1. TaxTypeAnnotation.java

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TaxTypeAnnotation {
    TaxType taxType();
}
Copy code

2.5.2. Strategy

The tax policy removes the registration method and adds a taxtype annotation annotation to identify the tax type.

public interface TaxStrategy {
    double calc(long amount);
}

@TaxTypeAnnotation(taxType = TaxType.INTER)
class InterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount * taxRate;
    }
}

@TaxTypeAnnotation(taxType = TaxType.OUTER)
class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Tax rate obtained
        return amount / (1 + taxRate) * taxRate;
    }
}
Copy code

2.5.3. Factory

public class AnnotationTaxStrategyFactory {
    // Storage tax policy
    static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>();

    static {
        registerTaxStrategy();
    }

    // Get the tax policy through map. When adding new tax policy, you don't need to modify the code. It is closed to modification, open to extension and follows the open close principle
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding a new tax type, you need to modify the code and increase the circle complexity
        if (taxStrategyMap.containsKey(taxType)) {
            return taxStrategyMap.get(taxType);
        } else {
            throw new Exception("The tax type is not supported.");
        }
    }

    // Automatic registration tax policy
    private static void registerTaxStrategy() {
        try {
            // Find all tax policy subclasses for registration through reflection
            Reflections reflections = new Reflections(new ConfigurationBuilder()
                    .setUrls(ClasspathHelper.forPackage(TaxStrategy.class.getPackage().getName()))
                    .setScanners(new SubTypesScanner()));
            Set<Class<? extends TaxStrategy>> taxStrategyClassSet = reflections.getSubTypesOf(TaxStrategy.class);

            if (taxStrategyClassSet != null) {
                for (Class<?> clazz: taxStrategyClassSet) {
                    // Find the tax type annotation and automatically complete the tax policy registration
                    if (clazz.isAnnotationPresent(TaxTypeAnnotation.class)) {
                        TaxTypeAnnotation taxTypeAnnotation = clazz.getAnnotation(TaxTypeAnnotation.class);
                        TaxType taxType = taxTypeAnnotation.taxType();
                        taxStrategyMap.put(taxType, (TaxStrategy)clazz.newInstance());
                    }
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            // Self defined exception handling
            e.printStackTrace();
        }
    }
}
Copy code

This method reduces the dependence of tax strategy and tax factory, only needs to pay attention to its own algorithm implementation, decoupling is the best.

2.6. Ultimate optimization and refinement of general design mode

In the software system, there are many similar strategies and algorithms, such as different tax calculation strategies, different encryption strategies, different XXX strategies, etc. these strategies can be unified and re abstracted to extract the public policy interface and policy factory class, so that there is no need for each strategy to have a set of code implementation, and it is enough to share a set of code.

This abstract design pattern can be called the self registration strategy pattern, and the actual code will not be written, leaving you to think about it (hint: use generics to abstract).

3. Summary
Note: no matter you are for Java high salary or hobbies, remember: project development experience is always the core. If you don't have the latest Java architecture practical video tutorial and the interview book of large factory, you can go to the small-scale Java architecture to learn. Skirt: seven umbrella bar, zero clothes and zero umbrella (Digital homophony) you can find it under conversion. There are many new Java architecture project tutorials in it. You can also communicate with the old driver for advice!  

  1. Map, instead of if, can improve scalability and reduce cycle complexity.
  2. The self registration strategy mode meets the opening and closing principle gracefully, which is closed to modification and open to expansion.
  3. The more familiar you are with the basic features of Java, the better you can think of solutions, such as the annotation features used in this article. Therefore, you should learn more about java basic features at ordinary times. Don't forget to understand new features if you think they are enough. New features must have its advantages, such as solving performance, elegant coding, decoupling, etc.

 

The text and pictures of this article come from the Internet and my own ideas. They are only for learning and communication. They have no commercial use. The copyright belongs to the original author. If you have any questions, please contact us in time for handling

Posted by Karve on Mon, 04 May 2020 11:06:47 -0700