Factory method pattern (Java version)

Overview and classification of design patterns: https://blog.csdn.net/qq_34896730/article/details/105324092

Object-oriented design principles: https://editor.csdn.net/md/?articleId=105352240
Simple factory pattern: https://blog.csdn.net/qq_34896730/article/details/105478464

1 Factory method model overview

      Consider such a system, a button factory class designed using a simple factory pattern can return a specific type of button instance, such as a round button, a rectangular button, a diamond button, and so on. In this system, if you need to add a new type of button, such as an oval button, then in addition to adding a new specific product class, you also need to modify the code of the factory class. This makes the whole design violate the opening and closing principle to a certain extent, as shown in Figure 1-1.

Figure 1-1 Button factory designed with simple factory pattern
      The system is now modified to no longer provide a button factory to be responsible for the creation of all products, but to hand over the creation process of specific buttons to a special factory subclass to complete, first define an abstract button factory class, and then Define specific factory classes to produce round buttons, rectangular buttons, diamond buttons, etc. They implement the methods declared in the abstract button factory class. The result of this abstraction is that this structure can introduce new products without modifying the specific factory class. If a new button type appears, you only need to define a specific factory class for this new type of button. Create an example of the button, this improved design scheme is the factory method pattern. The factory method pattern introduces an abstract factory class, which makes it superior to the simple factory pattern, and makes the system more in line with the opening and closing principles. The improved button factory is shown in Figure 1-2.
Figure 1-2 Button factory improved using the factory method pattern
      In the factory method mode, a unified factory class is no longer provided to create all product objects, but different factories are provided for different products. The system provides a factory hierarchy structure corresponding to the product hierarchy structure.

      The definition of the factory method pattern: define an interface for creating objects, but let subclasses decide which class to instantiate. The factory method pattern delays the instantiation of a class to its subclasses.
      The factory method pattern is abbreviated as the factory pattern (Factory Pattern), and can also be called the virtual constructor pattern (Virtual Constructor Pattern) or the polymorphic factory pattern (Polymorphic Factory Pattern). The factory method pattern is a creational pattern. In the factory method pattern, the factory parent class is responsible for defining the public interface for creating product objects, and the factory subclass is responsible for generating specific product objects. The purpose of this is to delay the instantiation of the product class until the factory subclass is completed, that is Use factory subclasses to determine which specific product subclass should be instantiated.

2 Factory method pattern structure and implementation

2.1 Factory method pattern structure

      The factory method pattern provides an abstract factory interface to declare abstract factory methods, and its subclasses implement concrete factory methods and create specific product objects. The structure diagram of the factory method pattern is shown in Figure 2-1.

Figure 2-1 Factory method pattern structure diagram
      As can be seen from Figure 2-1, the factory method pattern includes the following four roles.

      (1) Product (abstract product): It is the interface that defines the product, and it is the supertype of the object created by the factory method pattern, that is, the common parent class of the product object.
      (2) ConcreteProduct: It implements an abstract product interface. Certain types of concrete products are created by specialized concrete factories, and there is a one-to-one correspondence between concrete factories and concrete products.
      (3) Factory (abstract factory): A factory method (Factory Method) is declared in the abstract factory class, used to return a product. Abstract factories are the core of the factory method pattern, and all factory classes that create objects must implement this interface.
      (4) ConcreteFactory: It is a subclass of the abstract factory class, which implements the factory methods declared in the abstract factory, and can be called by the client to return an instance of a specific product class.

2.2 Factory method pattern implementation

      Compared with the simple factory pattern, the most important feature of the factory method pattern is the introduction of the abstract factory role. The abstract factory can be an interface, an abstract class, or a concrete class. The typical code is as follows:

public interface Factory{
   public Product factoryMethod();
}

      In the abstract factory, the factory method is declared but the factory method is not implemented. The creation of the specific product object is responsible for its subclasses. The client program for the abstract factory can specify the specific factory class at runtime. , Different specific factories can create different specific products. The typical code is as follows:

public class ConcreteFactory implements Factory{
   public Product factoryMethod(){
      return new ConcreteProduct();
   }
}

      In actual use, in addition to creating specific product objects, concrete factory classes can also be responsible for the initialization of product objects and some resource and environment configuration tasks, such as connecting to databases and creating files.
      In the client code, development only needs to care about the factory class, and different specific factories can create different products. A typical client code snippet is as follows:

...
Factory factory;
factory = new ConcreteFactory();//可通过配置文件与反射机制实现
Product product;
product = factory.factoryMethod();

      You can use the configuration file to store the concrete factory class ConcreteFactory class name, and then create a concrete factory object through the reflection mechanism, without changing the source code when replacing a new concrete factory. System expansion is more convenient.

3 Application example of factory method pattern

      1. An example shows that
      a system operation log recorder (Logger) can save the system operation log in various ways, for example, through a file record or a database record, and the user can flexibly change the log record mode by modifying the configuration file. When designing various loggers, the developer found that some initialization work was needed for the logger. The process of setting the initialization parameters was more complicated, and some parameters were set in strict order. Otherwise, recording failure might occur.
      In order to better encapsulate the initialization process of the logger and ensure the flexibility of switching between multiple loggers, the system is now designed using the factory method pattern (note: commonly used logging tools in Java are SLF4J, Log4j, etc.).
      2. Instance class diagram
      Through analysis, the structure diagram of this instance is shown in Figure 3-1.

Figure 3-1 Structure diagram of the logger
      In Figure 3-1, the Logger interface acts as an abstract interface, its subclasses FileLogger and DatabaseLogger act as concrete products, the LoggerFactory interface acts as an abstract factory, and its subclasses FileLoggerFactory and DatabaseLoggerFactory act as concrete factories.

      3. Example code
      (1) Logger: Logger interface, acting as an abstract product role.

public interface Logger{
   public void writeLog();
}

      (2) DatabaseLogger: Database logger, acting as a specific product role.

public class DatabaseLogger implements Logger{
   public void writeLog(){
       System.out.println("数据库日志记录.");
   }
}

      (3) FileLogger: File logger, acting as a specific product role.

public class FileLogger implements Logger{
    public void writeLog(){
      System.out.println("文件日志记录"); 
    }
}

      (4) LoggerFactory: Logger factory interface, acting as an abstract factory.

public interface LoggerFactory{
   public Logger createLogger();//抽象工厂方法
}

      (5) DatabaseLoggerFactory: database logging factory class, acting as a concrete factory role.

public class DatabseLoggerFactory implements LoggerFactory{
   public Logger createLogger(){
    //连接数据库,代码省略
    //创建数据库日志记录器对象
    Logger logger = new DatabaseLogger();
    //初始化数据库日志记录器,代码省略
    return logger;
   }
}

      (6) FileLoggerFactory: File logger factory class, acting as a concrete factory role.

public class FileLoggerFactory implements LoggerFactory{
   public Logger createLogger(){
     //创建文件日志记录器对象
     Logger logger = new FileLogger();
     //创建文件,代码省略
     return logger;
   }
}

      (7) Client: Client test class.

public class Client{
  public static void main(String args[]){
     LoggerFactory factory;
     Logger logger;
     factory = new FileLoggerFactory();//可引入配置文件和反射机制实现
     logger = factory.createLogger();
     logger.writeLog();
  }
}

      4. Results and analysis
      Compile and run the program, the output results are as follows: file log records.
      If you need to replace the logger, you only need to modify the class name of the specific factory class in the client code, for example, change FileLoggerFactory to DatabaseLoggerFactory, and the output result is as follows: database log records.
      If you need to add and use a new logger, you only need to add a new concrete factory class, and then modify the class name of the concrete factory class in the client code. The source code of the original class library
does not need to be modified.
      By introducing configuration files and using the reflection mechanism, it is possible to replace specific factory classes without modifying the client code. Make the system more in line with the principle of opening and closing, with better flexibility and scalability.

4 Reflection mechanism and configuration file

      In the logger example in the previous section, the client code needs to be modified when the logger is replaced. For the client, it does not meet the opening and closing principles. This section will introduce how to replace the client code without modifying any client code. Or add a new logging method.
      In actual application development, the instantiation process of specific factory classes can be improved, and the new keyword is not used directly in the client code to create factory objects. In the whole implementation process, two technologies are needed, namely Java reflection mechanism and configuration file.
      1. Java reflection mechanism
      Java reflection (Java Reflection) refers to a mechanism to obtain information about a class or an existing object of a known name while the program is running, including information such as class methods, properties, parent classes, and examples The creation and judgment of instance types, etc. The most used class in reflection is Class. Instances of the Class class represent the classes and interfaces in the running Java application. Its forName (String className) method can return the class or interface associated with the given string name. Class object, and then get a class instance through a class name string. For example, to create an object of type string, the code is as follows:

//通过类名生成实例对象并将其返回
Class c = Class.forName("java.lang.String");
Object obj = c.newInstance();
return obj;

      In addition, the java.lang.reflect package is also provided in the JDK , encapsulating other reflection-related classes.
      2. The configuration file of the configuration file
      software system is usually an XML file. You can use DOM (Document Object Model), SAX (Simple API for XML), StAX (Streaming API for XML) and other technologies to process XML files.
      In software development, you can store the class name in an XML configuration file, then read the configuration file to obtain the class name string, and then create an object through the Java reflection mechanism. For example, the specific Logger Factory class name FileLoggerFactory in the previous section is stored in the configuration file config.xml in the following XML format.

<!--config.xml-->
<?xml version="1.0"?>
<config>
   <className>designpatterns.factorymethod.FileLoggerFactory</className>
</config>

      In order to read the configuration file and generate an object by reflecting the class name string stored in it, you can create a tool class XMLUtil, the detailed code is as follows:

public class XMLUtil{
  //该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
  public static Object getBean(){
    try{
    //创建DOM文档对象
    DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = dFactory.newDocumentBuilder();
    doc = builder.parse(new File("src//designpatterns//factorymethod//config.xml"));
    //获取包含类名的文本节点
    NodeList nl = doc.getElementsByTagName("className");
    Node classNode = nl.item(0).getFirstChild();
    String cName = classNode.getNodeValue();
    //通过类名生成实例对象并将其返回
    Class c = Class.forName(cName);
    Object obj = c.newInstance();
    return obj;
    }catch(Exception e){
      e.printStackTrace();
      return null;
   }
  }
}

      With the XMLUtil class, you can modify the client code of the logger, instead of directly using the new keyword to create a specific factory class, but store the class name of the specific factory class in an XML file, and then through XMLUtil The static factory method getBean () of the class instantiates the object, and the code is modified as follows:

public class Client{
  public static void main(String args[]){
    LoggerFactory factory;
    Logger logger;
    factory = (LoggerFactory)XMLUtil.getBean();//getBean()的返回类型为Object需要进行强制类型转换
    logger = factory.createLogger();
    logger.writeLog();
  }
}

      After introducing the XMLUtil class and the XML configuration file, a new type of logging method is added if necessary. Only need to perform the following 4 steps:
      (1) The new logger needs to inherit the abstract logger Logger.
      (2) Correspondingly add a new concrete logger factory, inherit the abstract logger factory LoggerFactory, and implement the factory method createLogger (), set the initialization parameters and environment variables, and return the concrete logger object.
      (3) Modify the configuration file config.xml and replace the class name string of the original factory class with the newly added class name string of the specific logger factory class.
      The above refactoring fully conforms to the principle of opening and closing, which can make the system more flexible. Because many design patterns focus on the scalability and flexibility of the system, they all define an abstraction layer, declare business methods in the abstraction layer, and apply specific business methods. The implementation is placed in the implementation layer.

5 Overloading of factory methods

      In some cases, the same product class can be initialized in multiple ways. For example, the logger class mentioned in Section 3 can provide a default implementation for various loggers; it can also provide a database connection string for the database logger, and a file path for the file log; you can also set the relevant parameters Encapsulated into an object of type Object, the configuration parameters are passed into the factory class through the Object object. At this time, you can provide a set of overloaded factory methods to create product objects in different ways. Of course, for the same specific factory, no matter which factory method is used, the type of product created must be the same. The structure diagram of the overloaded factory method is shown in Figure 5-1.

Figure 5-1 Structure diagram of overloaded factory method
      After introducing the overloaded method, the code of the abstract factory class LoggerFactory is modified as follows:
public interfact LoggerFactory{
  public Logger createLogger();
  public Logger createLogger(String args);
  public Logger createLogger(Object obj);
}

      The code of the specific factory class DatabaseLoggerFactory is modified as follows:

public class DatabaseLoggerFactory implements LoggerFactory{
   public Logger createLogger(){
     //使用默认方式连接数据库,代码省略
     Logger logger = new DatabaseLogger();
     //初始化数据库日志记录器,代码省略
     return logger;
   }
   public Logger createLogger(String args){
     //使用参数args作为连接字符串来连接数据库,代码省略
     Logger logger = new DatabaseLogger();
     //初始化数据库日志记录器,代码省略
     return logger;
   }
   public logger createLogger(Object obj){
    //使用封装在参数obj中的连接字符串来连接数据库,代码省略
    Logger logger = new DatabaseLogger();
    //使用封装在参数obj中的数据来初始化数据库日志记录器,代码省略
    return logger;
   }
}

      In the abstract factory, multiple overloaded factory methods are declared, and these factory methods are implemented in the concrete factory. These methods can contain different business logic to meet the diverse creation needs of product objects.

6 Hidden factory methods

      Sometimes, in order to further simplify the use of the client, you can also hide the factory method from the client. At this time, the business method of the product class is directly called in the factory class. The client does not need to call the factory method to create the product object. The business method in the created product object.
      If the factory method is hidden from the client, the structure of the logger shown in Figure 3-1 can be modified to the structure shown in Figure 6-1.

Figure 6-1 Structure diagram of the logger after hiding the factory method
      In Figure 6-1, the code of the abstract factory class LoggerFactory is repaired as follows:
//将接口改为抽象类
public abstract class LoggerFactory{
  //在工厂类中直接调用日志记录器类的业务方法writeLog()
  public void writeLog(){
    Logger logger = this.createLogger();
    logger.writeLog();
  }
  public abstract Logger createLogger();
}

      The client code is modified as follows:

public class Client{
   public static void main(String args[]){
     LoggerFactory factory;
     factory=(LoggerFactory)XMLUtil.getBean();
     factory.writeLog();//直接使用工厂对象来调用产品对象的业务方法
   }
}

      By moving the business method call to the factory class, you can directly use the factory object to call the business method of the product object. The client does not need to use the factory method to create the product object. In some cases, this design can be used directly.

7 Advantages / disadvantages of the factory method model and applicable environment

      The factory method pattern is an extension of the simple factory pattern, which inherits the advantages of the simple factory pattern and also makes up for the shortcomings of the simple factory pattern. The factory method pattern is one of the most frequently used design patterns and is the core pattern of many open source frameworks and API libraries.

7.1 Advantages of the factory method model

      The advantages of the factory method pattern are as follows:
      (1) In the factory method pattern, the factory method is used to create the products that the customer needs, and also hides from the customer which specific product categories will be instantiated. The user only cares about what The factory corresponding to the product does not need to care about the creation details, or even need to know the class name of the specific product class.
      (2) Polymorphism design based on the role of the factory and the role of the product is the key to the factory method pattern. It allows the factory to determine which product object to create independently, and the details of how to create this object are completely encapsulated inside the specific factory. The factory method pattern is also called the polymorphic factory pattern precisely because all concrete factory classes have the same abstract parent class.
      (3) Another advantage of using the factory method pattern is that there is no need to modify the interfaces provided by abstract factories and abstract products when adding new products to the system, and there is no need to modify other specific factories and specific products. But, the scalability of the system becomes very good, fully in line with the principle of opening and closing.

7.2 Disadvantages of the factory method pattern

      The disadvantages of the factory method pattern are as follows:
      (1) When adding new products, you need to write new specific product categories, and also provide specific factory categories corresponding to them. The number of classes in the system will increase in pairs, to a certain extent. Increased the complexity of the system, there are more classes need to be compiled and run, will bring some additional overhead to the system.
      (2) Due to the scalability of the system, an abstraction layer needs to be introduced. The abstraction layer is used for definition in the client code, which increases the abstraction and difficulty of understanding the system.

7.3 Applicable environment of factory method

      You can consider using the factory method pattern in the following situations:
      (1) The client does not know the class of the object it needs. In the factory method mode, the client does not need to know the class name of the specific product class, but only needs to know the corresponding factory. The specific product object is created by the specific factory class, and the class name of the specific factory class can be stored in the configuration file Or in the database.
      (2) The abstract factory class specifies which object is created by its subclass. In the factory method pattern, the abstract factory class only needs to provide an interface for creating products, and its subclasses to determine the specific objects to be created, using object-oriented polymorphism and Lee's substitution principle, when the program runs The class object will override the parent class object, making the system easier to extend.

Published 59 original articles · won praise 20 · views 3618

Guess you like

Origin blog.csdn.net/qq_34896730/article/details/105604814