Don’t write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

Time:2020-9-29

Code purists! Isn’t it crazy to see dozens of if else in a class?
Can’t you learn design patterns? During the interview, you can only answer the simplest single instance mode. If you have used advanced features such as reflection, do you answer yes?
This time, let design patterns (template method pattern + factory pattern) and reflection help you eliminate if else!
It is really a useful dry goods in the development of super super super super!

The pit cargo

One day, a demand was received from the superior, which was overwhelming. The one-stop intelligent report query platform supports mysql, pgxl, tidb, hive, presto, Mongo and many other data sources. Any data you want can be found and displayed to you, which is of great significance to the data analysis of business personnel!
Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

Although the parameter verification, query engine and query logic of each data source are different, Pang Chuang pig is familiar with these frameworks, which is difficult for her. She only spent a day to write them all.

The leader of the fat rolling bear also affirmed the efficiency of the fat rolling pig. On the third day, the leader had nothing to do. He was ready to do a code review. The fat rolling bear was stunned. There were nearly 30 if else codes in a class. I dropped a damn, which broke the code purism maniac.

//Test the validity of participation
Boolean check = false;
if(DataSourceEnum.hive.equals(dataSource)){
    check = checkHiveParams(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
    check = checkTidbParams(params);
} else if(DataSourceEnum.mysql.equals(dataSource)){
    check = checkMysqlParams(params);
}// else if... Omit pgxl, presto, etc
if(check){
    if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    } else if(DataSourceEnum.mysql.equals(dataSource)){
        list = queryMysql(params);
    }// else if... Omit pgxl, presto, etc
}
//Log
log.info ("user = {} query data source = {} result size = {})", params.getUserName () params.getDataSource () list.size ());

Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

Template mode to save the field

First of all, let’s analyze. No matter what data source it is, the algorithm structure (process) is the same. 1. Verify the validity of parameters; 2. Query; 3. Log. This does not mean that the template is the same, but the specific details are different, right?

Let’s take a look at the definition of template method pattern in design pattern

Template method pattern: defines the framework of an algorithm in an operation and delays some steps to subclasses. The subclass can redefine some specific steps of an algorithm without changing the structure of the algorithm. Generally speaking, it is to put the same method of subclass into its abstract parent class.

Isn’t our requirement similar to the template method pattern? Therefore, we can extract the template into the parent class (abstract class). As for the implementation of specific steps, these special steps should be rewritten by subclasses.

No more nonsense. Let’s write the parent template first. The same logic is to record the log. This step is to write the template to death. As for test parameters and queries, these two methods are different, so they need to be set as abstract methods and rewritten by subclasses.

public abstract class AbstractDataSourceProcesser <T extends QueryInputDomain> {
    public List<HashMap> query(T params){
        List<HashMap> list = new ArrayList<>();
        //The validation logic of different engine SQL is different
        Boolean b = checkParam(params);
        if(b){
            //Inquiry
            list = queryData(params);
        }
        //Log
        log.info ("user = {} query data source = {} result size = {})", params.getUserName () params.getDataSource () list.size ());
        return list;
    }
    //Abstract methods implement specific logic by subclasses
    abstract Boolean checkParam(T params);
    abstract List<HashMap> queryData(T params);
}

This code is very simple. But in order to take care of the novice, I still want to explain one thing:

T this thing. It is called generics, because the input parameters of different data sources are different, so we use generics. But they also have common parameters, such as user names. Therefore, in order to avoid redundancy and make better use of common resources, we can have a Generic upper limit in the design of generics,<T extends QueryInputDomain>

public class QueryInputDomain<T> {
    Public string username; // query user name
    Public string datasource; // query data sources such as MySQL / tidb, etc
    Public t params; // different data source parameters are generally different
}
public class MysqlQueryInput extends QueryInputDomain{
    Private string database; // database
    public String sql;//sql
}

Next, it’s the turn of the subclass. According to the above analysis, it’s actually very simple. It’s just inheriting the parent class and overriding the checkparam() and querydata() methods. Take the MySQL data source as an example. Other data sources are also in the same routine:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
    @Override
    public Boolean checkParam(MysqlQueryInput params) {
        System.out.println ("check whether MySQL parameters are accurate");
        return true;
    }

    @Override
    public List<HashMap> queryData(MysqlQueryInput params) {
        List<HashMap> list = new ArrayList<>();
        System.out.println ("start to query MySQL data");
        return list;
    }
}

In this way, all data sources are self-contained and have a class of their own. It is very convenient and clear to expand data sources or modify the logic of a data source.

To tell you the truth, the template method pattern is too simple, and the abstract class is too basic and universal, and the general freshmen will know it. But for newcomers to the workplace, it is really not very decisive to apply it in actual production.Therefore, I would like to remind you: we must have an abstract thinking to avoid redundant code duplication.

In addition, it is easy for engineers who have worked for several years to make a mistake. It is to limit your thinking to today’s requirements. For example, the boss only gave you a MySQL data source query at the beginning, and there is no if else at all. Maybe you won’t pay attention to it and write it in a class directly, and you won’t consider subsequent extensions. It’s only when there are more and more new requirements that you suddenly realize that you have to reconstruct them all, which wastes your time.Therefore, I would like to remind you: do not be limited to today’s needs, but to consider the future. High scalability is achieved from the beginning, and subsequent requirements changes and maintenance are very cool.

Original statement: This article is the original blog post of “fat rolling pig learning programming”. Please indicate the source of reprint. Make programming vivid and interesting in the form of comics! Original is not easy, please pay attention!

Factory mode to save the market

However, the template mode still does not completely solve the if else problem of the fat boar, because it is necessary to determine which service implements the query logic according to the data source parameter passed in

  if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    }

So how to kill this if else? I’d like to tell you about the factory model.

Factory pattern: the factory method pattern is a pattern for creating objects, which is widely used in JDK and spring and struts frameworks. It transfers the creation of objects to the factory class.

In order to echo the word “factory”, I’d like to give you an example of a foundry factory so that you can have a deeper impression.

Take the mobile phone manufacturing industry as an example. We know that there are apple phones, millet phones and so on. The manufacturing methods of each brand of mobile phones are bound to be different. We can first define a standard interface of mobile phones, which has the make() method, and then different models of mobile phones inherit this interface

#Phone class: abstractproduct
public interface Phone {
    void make();
}
#Miphone class: manufacturing Xiaomi mobile phone (product1)
public class MiPhone implements Phone {
    public MiPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make xiaomi phone!");
    }
}
#IPhone category: making iPhone (product2)
public class IPhone implements Phone {
    public IPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make iphone!");
    }
}

Now there is a mobile phone OEM: [Tianba mobile phone OEM]. The customer will only tell the factory the model of the mobile phone, and it will match the production scheme of different models. Then how does the foundry realize it? In fact, it is also very simple. The simple factory mode (including the abstract factory mode and the factory method mode, can be understood if you are interested) is as follows:

#Phonefactory class: mobile phone factory
public class PhoneFactory {
    public Phone makePhone(String phoneType) {
        if(phoneType.equalsIgnoreCase("MiPhone")){
            return new MiPhone();
        }
        else if(phoneType.equalsIgnoreCase("iPhone")) {
            return new IPhone();
        }
    }
}

In this way, customers can tell you the model of the mobile phone, and you can call the method of the foundry class to get the corresponding mobile phone manufacturing class. You will find that in fact, it is only if else, but if else is extracted to a factory class, and the factory class creates objects in a unified way. This will not invade our business code, and it will be much better in terms of maintenance and aesthetics.

Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

First of all, we should add the spring container annotation @ component to each specific data source processor, such as mysqlprocessor and tidbprocessor. I think there is no need to explain this annotation. The key is: we can make different data sources into similar bean names, such asDatasourceprocessor ා data source name, as follows:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Component("dataSourceProcessor#tidb")
public class TidbProcesser extends AbstractDataSourceProcesser<TidbQueryInput>{

What are the benefits of this? I can use spring to help us load all beans inherited from abstractdatasourceprocessor at one time, such asMap<String, AbstractDataSourceProcesser>, key is the name of the bean, and value is the corresponding bean:

@Service
public class QueryDataServiceImpl implements QueryDataService {
    @Resource
    public Map<String, AbstractDataSourceProcesser> dataSourceProcesserMap;
    public static String beanPrefix = "dataSourceProcessor#";
    @Override
    public List<HashMap> queryData(QueryInputDomain domain) {
        AbstractDataSourceProcesser dataSourceProcesser = dataSourceProcesserMap.get(beanPrefix + domain.getDataSource());
        //Omit the query code
    }
}

Maybe you still don’t quite understand. Let’s take a look at the operation effect directly

1. The contents of the datasourceprocessermap are as follows. All data source beans are stored. Key is the name of the bean, and value is the corresponding bean:
Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

2. I can match the corresponding executor with key (prefix + data source name = beanname). For example, when the parameter datasource is tidb, the key isdataSourceProcessor#tidbAccording to the key, it can be obtained directly from the datasourceprocessermapTidbProcesser

Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

Don't write another pile of if else! Write about expulsion again! Two design patterns take you to eliminate it!

public static String classPrefix = "com.lyl.java.advance.service.";

AbstractDataSourceProcesser sourceGenerator = 
(AbstractDataSourceProcesser) Class.forName
(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource()))
.newInstance();

It should be noted that this method obtains the instance of the class through the classname, and the front-end parameter passing certainly does not pass the classname. Therefore, enumeration classes can be used to define the class names of different data sources

public enum DataSourceEnum {
    mysql("mysql", "MysqlProcesser"),
    tidb("tidb", "TidbProcesser");
    private String code;
    private String classz;

Original statement: This article is the original blog post of “fat rolling pig learning programming”. Please indicate the source of reprint. Make programming vivid and interesting in the form of comics! Original is not easy, please pay attention!

summary

Some children’s shoes always feel that the design pattern is useless, because in addition to crud or crud when writing code, you can only answer the simplest singleton pattern when you are asked about design pattern in the interview, and if you have used advanced features such as reflection, the answer is No.

In fact, the 23 Java design patterns are classic. Today, we solved the crash of if else with template method pattern + factory pattern (or reflection). In the follow-up study of design patterns, you should also practice more and find a place to use in real projects, so that you can really take your knowledge for yourself.

Although the content and technical points of this article are very simple, it aims to tell you that we should have a good abstract thinking of code. Avoid a pile of if else or other bad code in the code.

Even if you have a good abstract thinking of code, when developing requirements, you should not be limited to the present, only consider the present, but also think about the future scalability.

Just like you fall in love, you can be a responsible person only when you consider the present and the future

“May there be no scum in the world”

Original statement: This article is the original blog post of “fat rolling pig learning programming”. Please indicate the source of reprint. Make programming vivid and interesting in the form of comics! Original is not easy, please pay attention!

This article comes from the official account of fat programming. Make programming so easy and interesting in the form of comics.