Java geek


Related reading:

JAVA programming ideas (1) To increase the extensibility of JAVA programming ideas (2) To interface programming ideas (3) to remove the awkward if, The self-registration policy pattern satisfies the open and closed principle gracefully. (5) The event notification pattern decoupling process. (6) The singleton pattern uses the Java programming idea. (7) The Combination and inheritance scenarios Java concurrent programming introduction (eleven) stream limiting scenarios and Spring stream limiting implementation HikariPool source code (two) design ideas for reference in the workplace (a) IT factory survival rules


1. Examples of policy pattern prototypes

Now to implement a tax calculation strategy, the tax calculation types are in-price tax and out-of-price tax, and new tax types may be added in the future. The initial design structure of the class is as follows:

class Duties and responsibilities
TaxStrategy Tax Policy Interface
InterTaxStrategy In-price tax strategy, responsible for the calculation of in-price tax
OuterTaxStrategy Pricing tax strategy, responsible for pricing tax calculation
TaxType Tax type definition, currently only in-price tax and out-of-price tax
TaxStrategyFactory Tax strategy factory, according to the tax type to obtain different tax strategy to calculate the tax

Code 2.

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;  // Obtain the tax rate
        returnamount * taxRate; }}class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Obtain the tax rate
        return amount / (1+ taxRate) * taxRate; }}// Tax type definition
public enum TaxType {
    INTER, OUTER
}
Copy the code

2.2. Tax policy factory implemented by the IF statement

// Tax strategy factory
public class TaxStrategyFactory {
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When new tax types are added, code changes are required and cyclomatic complexity increases
        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 the code

As you can see, if you use the if statement to get different tax policies, you have to modify the existing code when you add a new tax policy, which is less attractive when there are many tax methods and increases cyclomatic complexity.

2.3. Map is used to replace IF in the first optimization of tax strategy factory

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

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

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

    // Obtain the tax policy from map. When adding a new tax policy, there is no need to modify the code. It is closed for modification, but open for extension
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding new tax types, you need to change the code and increase cyclomatic complexity
        if (taxStrategyMap.containsKey(taxType)) {
            return taxStrategyMap.get(taxType);
        } else {
            throw new Exception("The tax type is not supported."); }}}Copy the code

As you can see, after the evolution, the IF statement is gone, cyclomatic complexity is reduced, and the new policy is added by simply calling the policy registration interface. There is no need to change the code that gets the tax policy.

2.4. Automatic registration of secondary optimization policies

In the above implementation, to register a new tax policy, it is necessary to manually call the registration interface of MapTaxStrategyFactory. In this way, the existing code needs to be modified for each new tax policy, or an appropriate initial call point needs to be found to register the tax policy. How to perfectly comply with the open and close principle and close the modification, Open to extension?

After further optimization, the class structure is as follows:

class Duties and responsibilities
TaxStrategy Tax policy interface, provide tax calculation interface, and self-registered to the tax policy factory
InterTaxStrategy In-price tax strategy, responsible for the calculation of in-price tax
OuterTaxStrategy Pricing tax strategy, responsible for pricing tax calculation
TaxType Tax type definition, currently only in-price tax and out-of-price tax
AutoRegisterTaxStrategyFactory Tax policy factory, according to the tax type to obtain different tax policies to calculate taxes, while providing a tax policy registration interface

Here I look at the changed code:

Against 2.4.1. Tax policy

public interface TaxStrategy {
    double calc(long amount);
    // Add a self-registered interface
    void register(a);
}

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

    @Override public void register(a) {
        // Register yourself with the policy factory
        AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.INTER, this); }}class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Obtain the tax rate
        return amount / (1 + taxRate) * taxRate;
    }

    @Override public void register(a) {
        // Register yourself with the policy factory
        AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.OUTER, this); }}Copy the 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 {
    // The storage tax policy
    static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>();

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

    // Obtain the tax policy from map. When adding a new tax policy, there is no need to modify the code. It is closed for modification, but open for extension
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding new tax types, you need to change the code and increase cyclomatic complexity
        if (taxStrategyMap.containsKey(taxType)) {
            return taxStrategyMap.get(taxType);
        } else {
            throw new Exception("The tax type is not supported."); }}// Provide a tax registration policy interface. External users only need to call this interface to add a 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(a) {
        try {
            // Find all tax policy subclasses by reflection for registration
            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();// Call the self-registered method of the tax policytaxStrategy.register(); }}}catch (InstantiationException | IllegalAccessException e) {
            // Define your own exception handlinge.printStackTrace(); }}}Copy the code

Note: The following dependencies need to be added to the reflection tool in your code.

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

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

Use 2.4.3.

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

So far, when adding a new tax policy, there is no need to modify the existing tax policy factory code, basically perfect open closed principle, the only need to modify the tax type definition.

2.5. Three optimization to reduce coupling through annotations (suggested by netizen Gu Yuanyuan)

The basic idea is to use annotations on the tax policy to indicate which type of tax it is, and to automatically complete the registration of the tax policy according to the annotations in the tax policy factory without calling the registration interface of the tax policy factory in each tax policy. The class structure diagram is as follows:

Let’s look at the code that changed.

2.5.1. TaxTypeAnnotation. Java

import java.lang.annotation.*;

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

2.5.2. Strategy class

The tax policy removes the registration method and adds the TaxTypeAnnotation 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;  // Obtain the tax rate
        returnamount * taxRate; }}@TaxTypeAnnotation(taxType = TaxType.OUTER)
class OuterTaxStrategy implements TaxStrategy {
    @Override public double calc(long amount) {
        final double taxRate = 0.2;  // Obtain the tax rate
        return amount / (1+ taxRate) * taxRate; }}Copy the code

2.5.3. Factory class

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

    static {
        registerTaxStrategy();
    }

    // Obtain the tax policy from map. When adding a new tax policy, there is no need to modify the code. It is closed for modification, but open for extension
    public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception {
        // When adding new tax types, you need to change the code and increase cyclomatic 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(a) {
        try {
            // Find all tax policy subclasses by reflection for registration
            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 to 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) {
            // Define your own exception handlinge.printStackTrace(); }}}Copy the code

This approach reduces the dependence of tax strategy and tax factory, only need to focus on their own algorithm implementation, decoupling is the best.

2.6. Ultimate optimization and refinement of universal design patterns

In software system, similar to the above strategy algorithm will be a lot of, different tax policies, different encryption strategies, different strategies of XXX, etc., these can be unified abstraction again, extract the public interface of the strategy, strategy of the factory class, so there is no need for each strategy has a set of code realization, share a set of code is enough.

This abstract design pattern can be called the self-registration policy pattern, and the actual code is left to think it out.

3. Summary

  1. Map replaces IF for policy selection, which can improve scalability and reduce cyclomatic complexity.
  2. The self-registration policy model gracefully satisfies the open – closed principle, closed for modification and open for extension.
  3. The more familiar you are with basic Java features, such as the annotations feature used in this article, the better the solution. Therefore, you should learn more basic Features of JAVA at ordinary times, and do not think that you have enough to understand the new features, new features must have its advantages, such as performance, elegant coding, decoupling, and so on.

end.


<– read left mark, left point like!