Creation: Simple Factory Pattern

Catalog introduction
  • 01. Introduction to factory mode
  • 02. Use background instructions
  • 03. Introduction to pattern structure
  • 04. Simple Factory Pattern
  • 05. Advantages and disadvantages of simple factories

01. Introduction to factory mode

  • In general, the factory pattern is divided into three more subdivided types:

    • Simple factories, factory methods, and abstract factories. However, in GoF's "Design Patterns" book, it regards the simple factory pattern as a special case of the factory method pattern, so the factory pattern is only divided into two categories: factory method and abstract factory. In fact,
  • Among these three subdivided factory modes, simple factory and factory method are relatively simple in principle and are also commonly used in actual projects.

    • The principle of abstract factory is a little more complicated, and it is not commonly used in actual projects. Therefore, the focus of today's explanation is the first two factory modes. For abstract factories, you can understand a little bit.
  • The focus of the explanation is not the principle and implementation, because these are very simple, and the focus is to help you figure out the application scenario.

    • When should you use the factory pattern? What are the advantages of using the factory pattern to create objects instead of creating objects directly with new?

02. Use background instructions

  • Consider a simple software application scenario: a software system can provide multiple buttons with different appearances (such as round buttons, rectangular buttons, diamond buttons, etc.), these buttons are all derived from the same base class, but different after inheriting the base class Subclasses of .modify some properties so that they can appear differently.
  • If you want to use these buttons, you don't need to know the names of these specific button classes, you only need to know a parameter representing the button class, and provide a convenient method to call, and pass the parameter into the method to return a corresponding button Object, at this point, you can use the simple factory pattern.

03. Introduction to pattern structure

  • Factory: Factory role. The factory role is responsible for implementing the internal logic for creating all instances
  • Product: Abstract product role. The abstract product role is the parent class of all objects created and is responsible for describing the common interface common to all instances
  • ConcreteProduct: Concrete product role. A concrete product role is the target of creation, and all objects created act as instances of a concrete class for this role.

04. Simple Factory Pattern

  • First, let's see what is the simple factory pattern. Explain with an example. In the following code, we select different parsers (JsonRuleConfigParser, XmlRuleConfigParser...) according to the suffix of the configuration file (json, xml, yaml, properties), and parse the configuration stored in the file into the memory object RuleConfig.

     public class RuleConfigSource {
      public RuleConfig load(String ruleConfigFilePath) {
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
          parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
          parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
          parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
          parser = new PropertiesRuleConfigParser();
        } else {
          throw new InvalidRuleConfigException(
                 "Rule config file format is not supported: " + ruleConfigFilePath);
        }
    
        String configText = "";
        //从ruleConfigFilePath文件中读取配置文本到configText中
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
      }
    
      private String getFileExtension(String filePath) {
        //...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
      }
    }
  • In the "Specification and Refactoring" part, it was mentioned that in order to make the code logic clearer and more readable, we must be good at encapsulating independent code blocks into functions.

    • According to this design idea, we can strip out part of the logic involved in parser creation in the code and abstract it into the createParser() function. The refactored code looks like this:

       public RuleConfig load(String ruleConfigFilePath) {
      String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
      IRuleConfigParser parser = createParser(ruleConfigFileExtension);
      if (parser == null) {
        throw new InvalidRuleConfigException(
                "Rule config file format is not supported: " + ruleConfigFilePath);
      }
      
      String configText = "";
      //从ruleConfigFilePath文件中读取配置文本到configText中
      RuleConfig ruleConfig = parser.parse(configText);
      return ruleConfig;
      }
      
      private String getFileExtension(String filePath) {
      //...解析文件名获取扩展名,比如rule.json,返回json
      return "json";
      }
      
      private IRuleConfigParser createParser(String configFormat) {
      IRuleConfigParser parser = null;
      if ("json".equalsIgnoreCase(configFormat)) {
        parser = new JsonRuleConfigParser();
      } else if ("xml".equalsIgnoreCase(configFormat)) {
        parser = new XmlRuleConfigParser();
      } else if ("yaml".equalsIgnoreCase(configFormat)) {
        parser = new YamlRuleConfigParser();
      } else if ("properties".equalsIgnoreCase(configFormat)) {
        parser = new PropertiesRuleConfigParser();
      }
      return parser;
      }
      }
  • In order to make the responsibilities of the class more single and the code clearer, we can further strip the createParser() function into a separate class, and let this class only be responsible for the creation of objects. And this class is the simple factory pattern class we are going to talk about now. The specific code is as follows:

     public class RuleConfigSource {
      public RuleConfig load(String ruleConfigFilePath) {
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
        if (parser == null) {
          throw new InvalidRuleConfigException(
                  "Rule config file format is not supported: " + ruleConfigFilePath);
        }
    
        String configText = "";
        //从ruleConfigFilePath文件中读取配置文本到configText中
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
      }
    
      private String getFileExtension(String filePath) {
        //...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
      }
    }
    
    public class RuleConfigParserFactory {
      public static IRuleConfigParser createParser(String configFormat) {
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
          parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
          parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
          parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
          parser = new PropertiesRuleConfigParser();
        }
        return parser;
      }
    }
  • Most factory classes end with the word "Factory", but they are not required, such as DateFormat and Calender in Java. In addition, the methods of creating objects in factory classes generally start with create, such as createParser() in the code, but some are also named getInstance(), createInstance(), newInstance(), and some are even named valueOf () (such as the valueOf() function of the Java String class), etc., we name it according to specific scenarios and habits.
  • In the above code implementation, we need to create a new parser every time we call createParser() of RuleConfigParserFactory. In fact, if the parser can be reused, in order to save memory and object creation time, we can create and cache the parser in advance. When calling the createParser() function, we retrieve the parser object from the cache and use it directly.
  • This is somewhat similar to the combination of the singleton pattern and the simple factory pattern. The specific code implementation is as follows. In the following explanation, we call the previous implementation method the first implementation method of the simple factory pattern, and the following implementation method as the second implementation method of the simple factory pattern.

     public class RuleConfigParserFactory {
      private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
    
      static {
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        cachedParsers.put("yaml", new YamlRuleConfigParser());
        cachedParsers.put("properties", new PropertiesRuleConfigParser());
      }
    
      public static IRuleConfigParser createParser(String configFormat) {
        if (configFormat == null || configFormat.isEmpty()) {
          return null;//返回null还是IllegalArgumentException全凭你自己说了算
        }
        IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
        return parser;
      }
    }
  • For the implementation methods of the above two simple factory patterns, if we want to add a new parser, it is bound to change the code of RuleConfigParserFactory. Does this violate the open-closed principle? In fact, if it is not necessary to add new parser frequently, it is completely acceptable to modify the code of RuleConfigParserFactory occasionally, which is slightly inconsistent with the open-closed principle.
  • In addition, in the first code implementation of RuleConfigParserFactory, there is a set of if branch judgment logic, should it be replaced by polymorphism or other design patterns? In fact, it's perfectly acceptable to have if branches in your code if there aren't many if branches. The application of polymorphism or design pattern to replace the if branch judgment logic is not without any disadvantages. Although it improves the scalability of the code and is more in line with the open-closed principle, it also increases the number of classes, sacrificing the flexibility of the code. readability. We will discuss this in detail in a later chapter.
  • Although there are many if branch judgment logics in the code implementation of the simple factory pattern, which violates the open-closed principle, but the trade-off between scalability and readability, such code implementations in most cases (for example, do not need to add parser frequently, nor too many parsers) is no problem.

05. Advantages and disadvantages of simple factories

  • advantage:

    • By using the factory class, the outside world no longer needs to care about how to create various specific products. As long as a product name is passed as a parameter to the factory, a desired product object can be directly obtained, and the product object can be called according to the interface specification. All functions (methods) of .
    • The construction is easy and the logic is simple.
  • shortcoming:

    • 1. There are many if else judgments in the simple factory mode, which are completely Hard Code. If a new product is to be added, a new product class must be added at the same time, and the factory class must be modified, and then an else if branch can be added. , which violates the "Open-Closed Principle" principle of closed-to-modification. When the specific product categories in the system continue to increase, it is necessary to constantly modify the factory category, which is not conducive to the maintenance and expansion of the system.
    • 2. A factory class integrates the instance creation logic of all classes, which violates the principle of high cohesion responsibility assignment, and concentrates all creation logic into a factory class, and all business logic is implemented in this factory class . When it doesn't work, the whole system suffers. Therefore, it is generally only used in very simple cases, such as when the factory class is responsible for creating fewer objects.
    • 3. The simple factory pattern uses a static factory method, so that the factory role cannot form a hierarchical structure based on inheritance.
  • Applicable environment

    • The factory class is responsible for creating fewer objects: because fewer objects are created, the business logic in the factory method will not be too complicated.
    • The client only knows the parameters passed into the factory class, and does not care about how to create the object: the client does not need to care about the creation details, nor even the class name, and only needs to know the parameters corresponding to the type.

more content


杨充
221 声望42 粉丝