[Spring] understanding factory design patterns step by step

Keywords: Java Spring

What is a design pattern?

  • Semantic concept: classic code to solve specific problems in object-oriented design.
  • Narrow concept: 23 design modes defined by GOF4 Gang: factory, adapter, decorator, facade, agent, template

1, Factory design mode

What is factory design pattern?

Concept: create objects through factory classes;

Benefits: decoupling (coupling: Specifies the strong correlation between codes, and the change of one party will affect the other party.)

2, Create objects without using factory mode

Taking the creation of UserDao and UserService as an example, UserDao simulates the login operation of the database, and UserService simulates the related login business.

UserDao interface

public interface UserDao {
    void login(String name, String password);
}

UserDaoImpl implements the UserDao interface

public class UserDaoImpl implements UserDao{
    @Override
    public void login(String name, String password) {
        System.out.println("User login, name=" + name + ", password=" + password);
    }
}

UserService interface

public interface UserService {
    void login(String name, String password);
}

UserServiceImpl implements the UserService interface

public class UserServiceImpl implements UserService{
    UserDao userDao = new UserDaoImpl();

    @Override
    public void login(String name, String password) {
        System.out.println("Handle login business!");
        userDao.login(name, password);
    }
}

Test the Service created above.

@Test
public void testService() {
    // Suppose there's a lot of code

    // create object
    UserService userService = new UserServiceImpl();
    // Call method
    userService.login("Iron Man", "xxxxx");

    // Suppose there's a lot of code
}

The operation result is:

Handle login business!
User login, name=Iron Man, password=xxxxx

In the test file, you need to select the instantiation method of UserService. There is code coupling here. If you use other instantiation methods, you need to modify here. For example, if you want to use the instantiation method UserServiceImpl2() instead, the statement to create the object should be changed to:

UserService userService = new UserServiceImpl2();

At this time, it's really simple for us to modify it. We just need to change the instantiation method. However, if the code is very large, you need to find this paragraph in a large amount of code for modification, which will become very troublesome.

How to change the instantiation method without modifying this code? The factory mode needs to be introduced here.

3, Simple factory design

3.1 simple factory - create objects directly

At this point, you need to create a factory class and a static method in the project class to return an implementation class.

Factory design

public class BeanFactory {
    public static UserService getUserService() {
        return new UserServiceImpl();
    }
}

Create the test code and get the UserService object through getUserService() in the factory class.

@Test
public void testService2() {
    // Suppose there's a lot of code

    // create object
    UserService userService = BeanFactory.getUserService();
    // Call method
    userService.login("Iron Man", "xxxxx");

    // Suppose there's a lot of code
}

The operation results are as follows:

Handle login business!
User login, name=Iron Man, password=xxxxx

This solves the coupling problem above. If you use other implementation methods, you only need to modify the returned object in the factory class.

However, this transfers the coupling to the factory class and the objects that need to be modified in the factory class.

3.2 improvement Factory 1 - create objects by reflection

Next, the factory class is improved to create objects through reflection to achieve the effect of decoupling.

public class BeanFactory {
    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class clazz = Class.forName("com.zqc.basic.UserServiceImpl");
            userService = (UserService) clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userService;
    }
}

By de creating objects, the code is still coupled, and com.zqc.basic.UserServiceImpl is coupled in the factory class in the form of string. How to solve this problem?

3.3 improving factory 2 - Reflection and profile creation objects

You need to add a configuration class to the file, write com.zqc.basic.UserServiceImpl in the configuration class, and then modify the factory class.

Create a properties configuration file, which is a special Map in the form of key=value.

File name: applicationContext.properties (placed in the resources directory under Maven project)

userService = com.zqc.basic.UserServiceImpl

Modify factory class:

public class BeanFactory {
    private static Properties env = new Properties();

    // Static code block reading configuration file
    static {
        try {
            // Get IO input stream
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            // The file content is encapsulated in the Properties collection to read the file
            env.load(inputStream);
            // Close flow
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static UserService getUserService() {
        UserService userService = null;
        try {
            // Get objects through configuration files and reflection
            Class clazz = Class.forName(env.getProperty("userService"));
            userService = (UserService) clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userService;
    }
}

First, read the configuration file through the stream. Then, get the fully qualified class name by reading userService in the configuration file, and create the object through reflection.

So far, the coupling problem is solved. When we want to change the instantiation method, we only need to modify the configuration file.

4, General plant design

For the above UserService, we created the getUserService() method in the BeanFactory factory class. If OrderService is required, we also need to create getOrderService. If ChartService is required, we also need to create getChartService. In this way, a factory method should be created for each Service, which is very troublesome.

A simple factory needs to create a large number of factory methods, code redundancy, and many parts of these methods are repeated, so these methods need to be transformed into general methods.

Create a common factory code below:

public class BeanFactory {
    private static Properties env = new Properties();

    // Static code block reading configuration file
    static {
        try {
            // Get IO input stream
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            // The file content is encapsulated in the Properties collection to read the file
            env.load(inputStream);
            // Close flow
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
    public static Object getBean(String key){
        Object ret = null;
        try {
            // Get objects through configuration files and reflection
            Class clazz = Class.forName(env.getProperty(key));
            ret = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // Object type is returned
        return ret;
    }
}

The method of reading the configuration file remains unchanged, except that the type of the created Object is changed to Object, and the type of the returned Object is also Object. When you call getBean to return objects in a program, you need to make strong changes in its type.

Modify test file:

public class MyTest {
    @Test
    public void testService() {
        // Suppose there's a lot of code

        // create object
        UserService userService = (UserService) BeanFactory.getBean("userService");
        // Call method
        userService.login("Iron Man", "xxxxx");

        // Suppose there's a lot of code
    }
}

5, Summary

  1. Factory design pattern solves the problem of direct code coupling.
  2. The common factory mode is solved by configuration file + reflection.

The essence of Spring is a factory: ApplicationContext, which is configured by applicationContext.xml.

Posted by jack bro on Fri, 03 Sep 2021 14:34:23 -0700