Hello, nice to meet you.

Starting in September, I decided to start a Weekly blog with the goal of Posting at least one blog a week, either to read up on various source code sources or to document a problem I was working on.

After a period of aimless study, I found that it didn’t seem to be of much use. After a while, I forgot what I had read and didn’t take notes.

“What you learn, you bring out.” I think this is the best way for me to learn, and this is the origin of the Blog every Monday. Friends, if you often forget something you’ve seen before, join this activity.

This is my first blog post in December, and my first blog post on practicing design patterns.

  • This article was first published in Personal Speaker Knowledge Base: Back-end Technology as I understand it

In daily business iteration work, we often encounter such a need: based on the existing stable business to expand other business lines, so as to meet the needs of product and market expansion.

There are two main points here:

  • Stable process services with relatively fixed services exist
  • Develop similar businesses based on the first point

In such a scenario, if the code was utilitarian, it would be easy to create code like this:

if(Business A) {}else if(Business B) {}else if(Business C) {}else if(Business D) {}else if(Business E) {}...Copy the code

In fact, the same is true of the business code I see in the source code for company projects.

Therefore, in order to facilitate the development of similar businesses, I reconstructed this code through template mode.

1. Business background

The main function of the reconstructed business code is based on the data files provided by different manufacturers. Firstly, file format verification is carried out, and then analysis is carried out, and then the core data is saved. Finally, additional business processing is carried out depending on the situation.

In the original business code, there are three lengthy if-else judgments on file format verification, data file parsing, and additional business processing, which is a shock to the mind and body, as if impressed, convinced and completely overwhelmed.

2. Reconstruct the policy

As mentioned above, I refactor based on a template pattern.

In fact, the idea of refactoring originated in a Nuggets article “Eliminating if-else/ Switch-case from code”, and the refactoring strategy is based on this article. Thank you.

2.1 Abstract Template Service Interface

Based on the background of this business, I abstract out a common interface that covers all functions of this type of business.

public interface AnalysisDataFileInterface {

    /** * Prefixes: Check the data file format **@return boolean
     */
    boolean preValidFile(a);

    /** * Core business method: execute parse data file */
    void doAnalysis(a);

    /** * Indicates whether additional business needs to be performed **@return boolean
     */
    boolean doExtra(a);

    /** * Additional business */
    void extraBusiness(a);
}
Copy the code

It’s worth noting that the classic template pattern is implemented with abstract classes, with template methods set to final and other methods that require subclasses set to abstract, but in fact we can be more flexible with the template pattern in our projects, like I did with interfaces.

Implementing the template pattern through an interface has two benefits:

  • Circumventing the limitations of Java single inheritance in the abstract class implementation pattern
  • After JDK1.8, interfaces can have default implementations, and concrete business classes do not need to implement all methods in the interface

2.2 Specific business classes

Once you have defined the abstract interfaces for similar businesses, you can specify the concrete business classes. In this case, I extended two concrete business classes.

@Service("A")
public class ABusiness implements AnalysisDataFileInterface {

    private static final Logger logger = LoggerFactory.getLogger(ABusiness.class);

    /** * Prefixes: Check the data file format **@return boolean
     */
    @Override
    public boolean preValidFile(a) {
        logger.info("Pre-validation method for data files starting A business");
        return true;
    }

    /** * Core business method: execute parse data file */
    @Override
    public void doAnalysis(a) {
        logger.info("Start executing the core business of business A.");
    }

    /** * Indicates whether additional business needs to be performed **@return boolean
     */
    @Override
    public boolean doExtra(a) {
        return true;
    }

    /** * Additional business */
    @Override
    public void extraBusiness(a) {
        logger.info("Start performing additional business for business A"); }}@Service("B")
public class BBusiness implements AnalysisDataFileInterface {

    private static final Logger logger = LoggerFactory.getLogger(BBusiness.class);

    /** * Prefixes: Check the data file format **@return boolean
     */
    @Override
    public boolean preValidFile(a) {
        logger.info("Pre-validation method for data files starting B business");
        return true;
    }

    /** * Core business method: execute parse data file */
    @Override
    public void doAnalysis(a) {
        logger.info("Start executing the core business of business B");
    }

    /** * Indicates whether additional business needs to be performed **@return boolean
     */
    @Override
    public boolean doExtra(a) {
        return false;
    }

    /** * Additional business */
    @Override
    public void extraBusiness(a) {
        logger.info("Start performing additional business of business B"); }}Copy the code

I simply output A few lines of log in the concrete business class. Note that business A needs to perform additional business and business B does not, as determined by the doExtra() method.

Also, since this case is started in A SpringBoot environment, the specific business classes are injected into the Spring container via the @Service annotation with Bean names A and B.

2.3 Template Method

After completing the transformation of the generic interface of the specific business class, it is time to transform the service layer. As mentioned earlier, this is a similar business logic that consists of three steps:

  1. File format verification
  2. Parse data files, core business logic, save core data
  3. Perform additional business processing as required

Therefore, for such a fixed business, I used the template mode for transformation when expanding, and the code of the control layer after transformation is as follows:

In theory, the business logic code should sink into the service layer, which is put in the control layer for presentation purposes.

@RestController
public class BusinessController {

    / * * into all the specific business class implements AnalysisDataFileInterface interface * /
    @Resource
    private Map<String, AnalysisDataFileInterface> businessMap;

    /** * Select the appropriate concrete business class * based on the businessType argument passed in@param businessType
     * @return* /
    private AnalysisDataFileInterface chooseBusiness(String businessType) {
        return businessMap.get(businessType);
    }

    /** * External service interfaces *@param businessType
     * @return* /
    @GetMapping("/doBusiness")
    public String doBusiness(String businessType) {
        // 1. Select a service class based on the parameters passed in
        AnalysisDataFileInterface business = this.chooseBusiness(businessType);
        if (business == null) {
            return "failed: no such business";
        }
        // 2. Verify the file format
        if(! business.preValidFile()) {return "Failed: wrong file";
        }
        // 3. Perform core business
        business.doAnalysis();
        // 4. Perform additional services
        if (business.doExtra()) {
            business.extraBusiness();
        }
        return "success"; }}Copy the code

I transformed the external interface into a template approach that covers all similar businesses through four core common steps:

  1. Select the specified business class based on the parameters passed in
  2. Verification file format
  3. Perform core business
  4. Perform additional services

Steps 2 through 4 correspond to the three similar businesses mentioned above, but in the modified template method, only the first step is added, which selects the specified business class based on the parameters passed in. By inserting a value for AnalysisDataFileInterface interface Map, injected in the Spring container all the specific business class is the implementation of the interface, and then according to the incoming businessType choose each request corresponding business class, Executes the specified business in the template method.

In the transformation is complete, if you need to expand the backend only need to create a new implements AnalysisDataFileInterface interface class, writing in the business class specific business logic can doBusiness the core business of the interface code without making any changes will be able to fit.

Otherwise, before the transformation, the else judgment code block for the new business needs to be added in the three IF-else, and then the corresponding business code is called, which not only increases the complexity of the method, but also reduces its readability and increases the maintenance cost.

3. Template mode

3.1 define

Template Method Design Pattern (full name Template Method Pattern) is a framework for defining an algorithm in an operation, while deferring some steps to subclasses. Allows subclasses to redefine specific steps of an algorithm without changing its structure.

The template pattern consists of two roles:

  • Basic methods: Also called basic operations, are methods implemented by subclasses and called in template methods.
    • This is implemented in the refactoring code aboveAnalysisDataFileInterfaceInterface class.
  • Template methods: There can be one or more, usually a concrete method, that is, a framework that implements scheduling of basic methods and implements fixed logic.
    • The control layer method in the refactored code above.

3.2 the advantages

  1. Encapsulate invariant parts and extend variable parts

    • An algorithm that is considered an immutable part is encapsulated into a superclass implementation, while mutable parts can be extended through inheritance.
  2. Extract common parts of code for easy maintenance

  3. The behavior is controlled by the parent class and implemented by the child class

    • The base method is implemented by subclasses, so subclasses can be extended to add functionality in accordance with the open closed principle.

3.3 disadvantages

  • According to our design habits, abstract classes are responsible for declaring the most abstract and general thing properties and methods, and implementation classes do the concrete thing properties and methods. However, the template method pattern is reversed. Abstract classes define partial abstract methods, which are implemented by subclasses, and the results of the execution of the subclass affect the results of the parent class, that is, the subclass affects the parent class. This can make code reading difficult in complex projects, and also make beginners uncomfortable.

3.4 Application Scenarios

  1. Multiple subclasses have common methods and basically the same logic.

  2. For important and complex algorithms, the core algorithm can be designed as a template method, and the surrounding details are implemented by each subclass.

  3. In refactoring, the template method pattern is a frequently used pattern that extracts the same code into a parent class and then constrains its behavior with hook functions

4. Reference

  • Zen of design patterns
  • Eliminate if-else/switch-case in code