编程知识 cdmana.com

Using design patterns to eliminate if else in business code


preparation :
Suppose such a business scenario : There is an automatic billing function that needs to be implemented , In the program, you need to execute the corresponding processing logic according to the type of bill .


The following uses Lombok Simplify the code !!!


Enumeration of bill types :

/**
 * @author ly-az
 * @date 12/23/2020 11:34
 *  Bill type 
 */
public enum BillType {

    /**
     *  The type of Bill 
     */
    BT0001, BT0002

}


Billing :

/**
 * @author ly-az
 * @date 12/23/2020 11:31
 *  Billing entity 
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bill {

    /**
     *  Billing information 
     */
    String message;

    /**
     *  Bill type type type (BT0001、BT0002)
     */
    BillType type;

}


Simulation generation of bills : Used to generate simulation data

/**
 * @author ly-az
 * @date 12/23/2020 11:42
 *  Generate bills for testing 
 */
public class BillBuilder {
    /**
     *  Generating bills 
     *
     * @return  Bill collection 
     */
    public static List<Bill> generateBillList() {
        val billList = new ArrayList<Bill>();
        billList.add(new Bill(" I'm the first type of Bill ", BillType.BT0001));
        billList.add(new Bill(" I'm the second type of Bill ", BillType.BT0002));
        return billList;
    }
}


Use if-else Come true :

/**
 * @author ly-az
 * @date 12/24/2020 10:31
 * if-else
 */
public class Test01 {

    public static void main(String[] args) {
        List<Bill> billList = BillBuilder.generateBillList();
        billList.forEach(bill -> {
            if (bill.getType().equals(BillType.BT0001)) {
                //  Simulation to achieve business processing 
                System.out.println(" Start to deal with BT0001 Type of Bill  > > > " + bill.getMessage() + ". . . ");
                System.out.println(" Handle a successful !!!");
            } else if (bill.getType().equals(BillType.BT0002)) {
                System.out.println(" Start to deal with BT0002 Type of Bill  > > > " + bill.getMessage() + ". . . ");
                System.out.println(" Handle a successful !!!");
            }
            // ...  There are countless more in the future else if Branch  ...
            else {
                System.out.println(" Bill type does not match !!!");
            }
        });
    }

}

In case of if-else When the branch business logic of is complex , In order to make the code readable, we will organize it into a method or encapsulate it into a tool class to call , Avoid the whole if-else The structure is too bloated ( There are two types simulated here , lazy ...ψ(`∇´)ψ).
But when there are more and more types of bills ,if-else There will be more and more branches , Every time you add a type , You need to modify or add if-else Branch , A violation of the Opening and closing principle ( Open to expansion , Turn off for changes )
**

Scheme 1 : Using the policy pattern +Map Dictionaries

Policy patterns belong to the behavior patterns of objects . It is intended for a set of algorithms , Encapsulate each algorithm into a separate class with a common interface , So that they can replace each other . The policy mode enables the algorithm to change without affecting the client .
------《JAVA With the model 》


That is to say, let our own code no longer depend on the client when calling the business code , It can execute different code according to different clients , The client only needs to call our policy interface .


In the above scenario , You can put if-else Branch code extraction for a variety of different strategies to achieve , Moreover, we can extract the code logic of the implementation of different strategies into a factory class , This is the strategy pattern + Simple factory model , To further simplify , We can also put the implementation of the strategy into a Map In the dictionary


Look at the code :

  • Abstract strategy : Usually implemented by an interface or abstract class . It is the interface required by all specific policy classes .
/**
 * @author ly-az
 * @date 12/23/2020 11:46
 * @see BillHandleStrategyFactory  Strategy factory 
 *  Policy interface for bill processing <br>
 *  When there is a new type of bill, you only need to implement this interface, rewrite the implementation method in the interface and add it to the dictionary in the policy factory 
 */
public interface BillHandleStrategy {
    /**
     *  processing method 
     *
     * @param bill  bill 
     */
    void handleBill(Bill bill);
}
  • Environmental Science : Holding a reference to a policy interface .
/**
 * @author ly-az
 * @date 12/23/2020 11:46
 */
public class BillStrategyContext {

    private BillHandleStrategy billHandleStrategy;

    /**
     *  Set policy interface 
     *
     * @param billHandleStrategy  Policy interface 
     */
    public void setBillHandleStrategy(BillHandleStrategy billHandleStrategy) {
        this.billHandleStrategy = billHandleStrategy;
    }

    public void handleBill(Bill bill) {
        if (billHandleStrategy != null) {
            billHandleStrategy.handleBill(bill);
        }
    }
}
  • Specific strategy implementation : It is the concrete implementation of the policy interface for handling bills , The processor of the bill .
/**
 * @author ly-az
 * @date 12/23/2020 13:01
 *  Handle BT0001 Implementation class of the policy interface of the bill 
 */
public class Bt0001BillHandleStrategy implements BillHandleStrategy {

    @Override
    public void handleBill(Bill bill) {
        //  Simulate business processing 
        System.out.println(" Start to deal with BT0001 Type of Bill  > > > " + bill.getMessage() + ". . .");
        System.out.println(" Handle a successful !!!");
    }

}
/**
 * @author ly-az
 * @date 12/23/2020 13:02
 *  Handle BT0002 Policy implementation class of type 
 */
public class Bt0002BillHandleStrategy implements BillHandleStrategy {

    @Override
    public void handleBill(Bill bill) {
        //  Simulation to achieve business processing 
        System.out.println(" Start to deal with BT0002 Type of Bill  > > > " + bill.getMessage() + ". . . ");
        System.out.println(" Handle a successful !!!");
    }
}

  • Strategy factory : The implementation class object used to obtain the specific policy
/**
 * @author ly-az
 * @date 12/23/2020 13:05
 *  Strategy factory 
 */
public class BillHandleStrategyFactory {

    private static final Map<BillType, BillHandleStrategy> BILL_HANDLE_STRATEGY_MAP;

    static {
        //  initialization ,
        BILL_HANDLE_STRATEGY_MAP = new HashMap<>();
        BILL_HANDLE_STRATEGY_MAP.put(BillType.BT0001, new Bt0001BillHandleStrategy());
        BILL_HANDLE_STRATEGY_MAP.put(BillType.BT0002, new Bt0002BillHandleStrategy());
    }


    /**
     *  According to the bill type, directly from Map Get the corresponding processing strategy implementation class from the dictionary 
     *
     * @param billType  Bill type 
     * @return  Implementation class for handling policies 
     */
    public static BillHandleStrategy getBillHandleStrategy(BillType billType) {
        return BILL_HANDLE_STRATEGY_MAP.get(billType);
    }
}


Test code :**

/**
 * @author ly-az
 * @date 12/23/2020 13:12
 */
public class Test02 {

    public static void main(String[] args) {
        // Simulation Bill 
        List<Bill> billList = BillBuilder.generateBillList();
        // Policy context 
        BillStrategyContext billStrategyContext = new BillStrategyContext();
        billList.forEach(bill -> {
            // Get and set policies 
            BillHandleStrategy billHandleStrategy = BillHandleStrategyFactory.getBillHandleStrategy(bill.getType());
            billStrategyContext.setBillHandleStrategy(billHandleStrategy);
            // Execution strategy 
            billStrategyContext.handleBill(bill);
        });
    }
}


test result :



Whenever there is a new type of Bill , Just add a new billing strategy , To add to BillHandleStrategyFactory Medium Map aggregate .
If you want the program to conform to the open close principle , You need to adjust BillHandleStrategyFactory How to get the processing strategy in .
( ok , I have a showdown , In fact, I was too busy to think of how to write ヾ(•ω•`)o)

Improve your thinking : The strategy pattern + annotation , Through custom annotations , Use annotations to mark specific policy implementation classes , We can get the concrete instance of policy implementation class through reflection , And put it in the container Map In the dictionary !

Option two : Use the responsibility chain model

seeing the name of a thing one thinks of its function , The chain of responsibility model (Chain of Responsibility Pattern) Create a chain of receiver objects for the call request . Make it possible for multiple objects to receive requests , And connect these objects into a chain , And pass the call request along the chain , Until an object processes it . Behavior patterns are also of this type .
The client making the call request does not know which object in the chain will eventually process the call request , This allows our program to dynamically reorganize and assign responsibilities without affecting the client .

  • Image interface for bill processing
/**
 * @author ly-az
 * @date 12/23/2020 14:51
 *  Abstract processing interface for bills 
 */
public interface IBillHandler {

    /**
     *  An abstract way to handle bills with a chain of responsibility 
     * @param bill  bill 
     * @param handleChain  Processing chain 
     */
    void handleBill(Bill bill, IBillHandleChain handleChain);

}
  • Specific implementation of bill processing
/**
 * @author ly-az
 * @date 12/23/2020 15:14
 *  Implementation of the specific processor of the bill 
 */
public class Bt0001BillHandler implements IBillHandler {

    @Override
    public void handleBill(Bill bill, IBillHandleChain handleChain) {
        if (BillType.BT0001.equals(bill.getType())) {
            //  Simulation to achieve business processing 
            System.out.println(" Start to deal with BT0001 Type of Bill  > > > " + bill.getMessage() + ". . . ");
            System.out.println(" Handle a successful !!!");
        }
        // If you can't handle the receipt, pass it down 
        else {
            handleChain.handleBill(bill);
        }
    }
}
/**
 * @author ly-az
 * @date 12/23/2020 15:42
 * todo
 */
public class Bt0002BillHandler implements IBillHandler {

    @Override
    public void handleBill(Bill bill, IBillHandleChain handleChain) {
        if (BillType.BT0002.equals(bill.getType())) {
            //  Simulation to achieve business processing 
            System.out.println(" Start to deal with BT0002 Type of Bill  > > > " + bill.getMessage() + ". . . ");
            System.out.println(" Handle a successful !!!");
        }
        // If you can't handle the receipt, pass it down 
        else {
            handleChain.handleBill(bill);
        }
    }
}

  • The chain of responsibility interface
/**
 * @author ly-az
 * @date 12/23/2020 14:52
 *  The chain of responsibility interface 
 */
public interface IBillHandleChain {
    /**
     *  An abstract way to handle bills 
     *
     * @param bill  bill 
     */
    void handleBill(Bill bill);
}


  • The chain of responsibility for implementing the interface
/**
 * @author ly-az
 * @date 12/23/2020 15:08
 *  The chain of responsibility for handling bills 
 */
public class BillHandleChain implements IBillHandleChain {
    /**
     *  Record the current processor location 
     */
    private int index = 0;
    /**
     *  Processor set 
     */
    private static final List<IBillHandler> BILL_HANDLER_LIST;

    static {
        // Get the processor object from the container 
        BILL_HANDLER_LIST = BillHandlerContext.getBillHandlerList();
    }

    @Override
    public void handleBill(Bill bill) {
        if (BILL_HANDLER_LIST != null && BILL_HANDLER_LIST.size() > 0) {
            if (index != BILL_HANDLER_LIST.size()) {
                IBillHandler billHandler = BILL_HANDLER_LIST.get(index++);
                billHandler.handleBill(bill, this);
            }
        }
    }
}

  • Chain of responsibility handler container ( If the project has Spring Of IOC Containers , Then we can get it directly through dependency injection IBillHandler The concrete realization of )
/**
 * @author ly-az
 * @date 12/23/2020 15:43
 *  Handling containers 
 */
public class BillHandlerContext {

    private BillHandlerContext() {
    }
    
    public static List<IBillHandler> getBillHandlerList() {
        val billHandlerList = new ArrayList<IBillHandler>();
        billHandlerList.add(new Bt0001BillHandler());
        billHandlerList.add(new Bt0002BillHandler());
        return billHandlerList;
    }

}
  • The test case
/**
 * @author ly-az
 * @date 12/23/2020 15:48
 *  Testing in the chain of responsibility mode 
 */
public class TestClient2 {

    public static void main(String[] args) {
        List<Bill> billList = BillBuilder.generateBillList();
        billList.forEach(bill -> {
            // Instance object of receipt processing chain 
            BillHandleChain billHandleChain = new BillHandleChain();
            billHandleChain.handleBill(bill);
        });
    }

}


result :
image.png


Again , If you want the program to conform to the open close principle , You need to adjust BillHandlerContext How to get the processor in , By way of reflection , Get all the IBillHandler Implementation class of .
**
o( ̄▽ ̄)ブ, I thought about how to modify this solution to meet the opening and closing principle !!!


We need to introduce a tool class for reflection :

/**
 * @author ly-az
 * @date 12/23/2020 16:00
 *  Reflection tools 
 */
public class ReflectionUtil {

    /**
     *  Define a collection of classes ( Used to store the image of all loaded classes )
     */
    private static final Set<Class<?>> CLASS_SET;

    static {
        // Specify the load package path 
        CLASS_SET = getClassSet("com.az");
    }

    /**
     *  Gets the class loader 
     *
     * @return  Class loader 
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     *  Load class 
     *
     * @param className      Class fully qualified name 
     * @param isInitialized  Whether to execute the static code block after loading 
     * @return  A mirror image of a class 
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return cls;
    }

    public static Class<?> loadClass(String className) {
        return loadClass(className, true);
    }

    /**
     *  Get all classes under the specified package 
     *
     * @param packageName  Full package name 
     * @return  mirrored Set aggregate 
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if ("file".equals(protocol)) {
                        String packagePath = url.getPath().replace("%20", "");
                        addClass(classSet, packagePath, packageName);
                    } else if ("jar".equals(protocol)) {
                        JarURLConnection jarUrlConnection = (JarURLConnection) url.openConnection();
                        if (jarUrlConnection != null) {
                            JarFile jarFile = jarUrlConnection.getJarFile();
                            if (jarFile != null) {
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (jarEntryName.endsWith(".class")) {
                                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                        doAddClass(classSet, className);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return classSet;
    }

    private static void doAddClass(Set<Class<?>> classSet, String className) {
        Class<?> cls = loadClass(className, false);
        classSet.add(cls);
    }

    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
        final File[] fileList = new File(packagePath).listFiles(file -> (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory());
        assert fileList != null;
        Arrays.asList(fileList).forEach(file -> {
            String fileName = file.getName();
            if (file.isFile()) {
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (StringUtils.isNotEmpty(packageName)) {
                    className = packageName + "." + className;
                }
                doAddClass(classSet, className);
            } else {
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        });
    }


    public static Set<Class<?>> getClassSet() {
        return CLASS_SET;
    }

    /**
     *  Get a parent class under the application package name ( Or interface ) All subclasses of ( Or implementation classes )
     *
     * @param superClass  Parent class ( Or interface ) Mirror image 
     * @return  All subclasses ( Or implementation classes ) The mirror set of 
     */
    public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> cls : CLASS_SET) {
            if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

    /**
     *  Get the class with some annotation under the application package name 
     *
     * @param annotationClass  The mirror image of annotation 
     * @return  Mirror collection 
     */
    public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> cls : CLASS_SET) {
            if (cls.isAnnotationPresent(annotationClass)) {
                classSet.add(cls);
            }
        }
        return classSet;
    }

}

Modify the handling container of responsibility chain mode :

/**
 * @author ly-az
 * @date 12/23/2020 15:43
 *  Handling containers 
 */
public class BillHandlerContext {

    private BillHandlerContext() {
    }

    public static List<IBillHandler> getBillHandlerList() {
        val billHandlerList = new ArrayList<IBillHandler>();
        // obtain IBillHandler Implementation class of interface 
        Set<Class<?>> classList = ReflectionUtil.getClassSetBySuper(IBillHandler.class);
        if (classList.size() > 0) {
            classList.forEach(clazz -> {
                try {
                    billHandlerList.add((IBillHandler) clazz.getDeclaredConstructor().newInstance());
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    e.printStackTrace();
                }
            });
        }
        return billHandlerList;
    }

}

thus , The chain of responsibility scheme is in line with the principle of opening and closing , If you add a billing type , Just add a new implementation class for the billing processor , No other changes are required .

Summary

if-else as well as switch-case This method of branch judgment is suitable for simple business with little branching logic , Or more intuitive and efficient . But for business complexity , When there are many branch logics , Adopt the appropriate design pattern , Will make the code clearer , Easy to maintain , But it's also a multiplication of classes . We need to do a good job of business analysis , Avoid designing patterns in the first place , Avoid over design !

版权声明
本文为[ly-az]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224140210501l.html

Scroll to Top