Design pattern learning notes the differences between simple factory, factory method and abstract factory pattern in factory pattern

Factory Design pattern in design pattern is a commonly used creative design pattern, which can be divided into three types: Simple Factory, factory method and abstract factory. So what's the difference between the three? Let's start with the conclusion:

  • Simple factory: there is only one factory (simple factory), a product interface / abstract class, which creates specific product objects according to the static methods in the simple factory. It is suitable for scenarios with few products and little expansion
  • Factory method: there are multiple factories (Abstract Factory + multiple concrete factories) and one product interface / abstract class. The concrete product object is created polymorphically according to the methods in the inherited abstract factory. Applicable to multiple products of one type
  • Abstract method: there are multiple factories (Abstract Factory + multiple concrete factories) and multiple product interfaces / abstract classes. Product subclasses are grouped, and different concrete product objects in the same group are created according to the polymorphism of methods in the inherited abstract factory. Applicable to multiple products of multiple types

The following is a detailed description

1, Simple Factory Pattern

1.1 introduction to simple factory mode

The factory mode is used to receive the static parameters of different factory methods, which are also called static methods. Let's explain it through a class diagram:

  • Product interface: defines the interface of the product object to be created
  • ProductA, ProductB and ProductC product classes: specific products that implement product interfaces and have product interface characteristics
  • SimpleFactory simple factory: there is only one factory, and specific product objects are created through the static method createProduct
  • Client client: there are multiple clients. Each client can create specific product objects through a simple factory

1.2 implementation of simple factory mode

Take the above class diagram as an example to realize the simple factory mode:

/**Product interface**/
public interface Product {

    void doSomething();
}

/**Specific product realization**/
class ProductA implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductA");
    }
}

class ProductB implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductB");
    }
}

class ProductC implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductC");
    }
}
/**Simple factory**/
public class SimpleFactory {
    /**Factory class to create product static method**/
    public static Product createProduct(String productName) {
        Product instance = null;
        switch (productName){
            case "A":
                instance = new ProductA();
                break;
            case "B":
                instance = new ProductB();
                break;
            case "C":
                instance = new ProductC();
        }
        return instance;
    }
    /**The client calls the factory class**/
    public static void main(String[] args) {
        SimpleFactory simpleFactory = new SimpleFactory();
        createProduct("A").doSomething();
        createProduct("B").doSomething();
    }
}
  • Advantages: the simple factory can exempt the client from the responsibility of directly creating objects, and can create corresponding products according to needs. Realize the separation of client and product code. In addition, the configuration file can be used to add new specific product classes (improvements) without modifying the client code.

  • Disadvantages: Contrary to the opening and closing principle, if you need to add other product classes, you must add if else logic judgment in the factory class (which can be improved through the configuration file). But overall, system expansion is still more difficult than other factory modes.
    We found that factory classes in the simple factory pattern use static methods, so why do you do this? Can non static methods be used?

  • Using static methods can create objects without using new, which is convenient to call

  • Static methods mean that instance objects can be obtained directly. Non static methods can only be called through construction methods (generally private) and cannot be accessed outside the factory class

  • For some scenes that are sensitive to instantiating and destroying objects, such as database connection pool, instantiated objects can be used repeatedly and stably

To sum up, the simple factory model is applicable to the situation that the business is simple and the product is fixed, and the factory class will not be changed often.

1.3 simple factory mode usage scenario

Let's take a look at the business scenarios in which the simple factory model is generally used

1.3.1 JDK, Spring and other source codes

There is such a design in Java. For example, this method in DateFormat is the application of a simple factory

private static DateFormat get(LocaleProviderAdapter adapter, int timeStyle, int dateStyle, Locale loc) {
    DateFormatProvider provider = adapter.getDateFormatProvider();
    DateFormat dateFormat;
    //Logical judgment implements that specific object
    if (timeStyle == -1) {
        dateFormat = provider.getDateInstance(dateStyle, loc);
    } else {
        if (dateStyle == -1) {
            dateFormat = provider.getTimeInstance(timeStyle, loc);
        } else {
            dateFormat = provider.getDateTimeInstance(dateStyle, timeStyle, loc);
        }
    }
    return dateFormat;
}

In addition, Calender and others can also see some classes ending in "Factory" in the Spring source code, which are the use of Factory mode.

1.3.2 database connection pool

For example, it is easier to connect to the database in the mode of db3p0 and fixed database when the mode of db3p0 and fixed database is not required. For example, it is easier to connect to the database in this mode.

2, Factory Method Pattern

We know that the simple factory mode violates the opening and closing principle and is not easy to expand. Therefore, there is no simple factory mode among the 23 GOF design modes. Let's take a look at another factory mode: factory method mode

2.1 introduction to plant method mode

The problem that the abstract factory pattern needs to solve is the problem of interface selection when there are multiple different types of products in a product family.

The factory method pattern is actually an upgrade of the simple factory pattern. In addition to the product interface, the factory method pattern also defines an interface for creating an object factory, so that the factory subclass can instantiate the corresponding product class. Explain through class diagram:

  • Product interface: the same as a simple factory, it provides an interface for product objects
  • ProductA, ProductB, and productC: specific types of product objects
  • FactoryA, FactoryB and FactoryC: specific product factories that implement specific product objects
  • AbstractFactory: abstract factory. There can be more than one. The method is responsible for returning the created product object
  • Client: the client using this mode

2.2 factory method mode implementation

According to the above class diagram, we can implement corresponding codes:

/**Product interface**/
public interface Product {

    void doSomething();
}

/**Specific product realization**/
class ProductA implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductA");
    }
}

class ProductB implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductB");
    }
}

class ProductC implements Product{

    @Override
    public void doSomething() {
        System.out.println("I am ProductC");
    }
}

/**Factory interface**/
public interface AbstractFactory {
	/**Create a Product method, which is different from the static method of factory mode**/
    public Product createProduct();
}

/**Specific factory implementation**/
class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}

class FactoryA implements AbstractFactory{

    @Override
    public Product createProduct() {
        return new ProductA();
    }
}
/**Client call factory**/
public class Client {
    public static void main(String[] args) {
        Product productA = new FactoryA().createProduct();
        productA.doSomething();
        Product productB = new FactoryB().createProduct();
        productB.doSomething();
    }
}

The most important one is the createProduct method in the AbstractFactory class. This method is used to generate specific products, which is why it is called the factory method. Different from the static method of a simple factory, here is the non static calling method used. Moreover, it can be found that without the if else logic judgment in a simple factory, the scalability is much stronger.

  • Advantages: fully realize the opening and closing principle, and realize the scalable and more complex hierarchical structure. Clear responsibilities, polymorphic, applicable to any entity class.
  • Disadvantages: if the business increases, the number of classes in the system will be doubled and the complexity of the code will be increased

2.3 factory method mode usage scenario

2.3.1 Slf4j

In Slf4j, a logging framework that we often use, there is the application of factory method pattern, such as obtaining logger object instances with high frequency:

private Logger logger = LoggerFactory.getLogger(Client.class);

Click the source code and we will find the getLogger method:

//Simple factory mode
public static Logger getLogger(String name) {
    /**Use of factory method mode**/
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
//Factory interface
public interface ILoggerFactory {
    Logger getLogger(String var1);
}
//Logger product interface
public interface Logger {
    String ROOT_LOGGER_NAME = "ROOT";
    ...
}

You need to call the factory method interface to implement the specific logger object instance, which is a typical application of a factory method pattern

2.3.2 some rule configuration analysis

When different types of rule configuration resolution are required, we can also use the factory method pattern, such as the code quoting the beauty of design patterns:

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);

    IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);
    }
    IRuleConfigParser parser = parserFactory.createParser();

    String configText = "";
    //Read the configuration text from the ruleConfigFilePath file into configText
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //... Resolve the file name to obtain the extension, such as rule json, return json
    return "json";
  }
}

//Because the factory class contains only methods and no member variables, it can be reused,
//There is no need to create new factory class objects every time, so the second implementation idea of simple factory mode is more appropriate.
public class RuleConfigParserFactoryMap { //Factory factory
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();

  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }

  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

When you need to add a new rule configuration parser, you only need to create a new parser class and a parserfactory to complete different configurations

3, Abstract factory pattern

The abstract factory mode is not as common as the simple factory and factory method modes. The scenario is relatively special. In the simple factory and factory method, there is only one classification method for classes. For example, in the simple factory, it is divided into ProductA, ProductB and ProductC according to the product type. However, if there are multiple classification methods, for example, according to the manufacturer of the product, ProductA may be in the same category as ProductC. This uses the abstract factory pattern

3.1 introduction to abstract factory mode

Abstract Factory Pattern belongs to creation pattern. It is actually an extension of factory method pattern. It is equivalent to a super factory, which is used to create patterns of other factories. In the Abstract Factory Pattern, the interface is the factory responsible for creating a related object, and each factory can provide objects according to the factory pattern. In fact, the abstract factory is also to reduce the number of subclasses and factory classes in the factory method. The design pattern proposed based on this is shown in the following figure (source Amoy Technology):

For example, in the factory method, we can only classify according to the keyboard, host and display, which will lead to a large number of factory classes and product subclasses. The abstract factory can group the above three product classes, which can greatly reduce the number of factory classes. Let's take a look at the corresponding class diagram:

  • Product1 and Product2: define a type of product object interface
  • Product1A, Product1B, etc.: various types of specific product objects
  • FactoryA and FactoryB: specific product factories, responsible for creating product objects under this factory type
  • AbstractFactory: an abstract factory interface that defines a class of product objects
  • Client: the client uses the abstract factory to call the product object

3.2 implementation of abstract factory pattern

According to the above class diagram, use the code to realize the abstract factory:

/**Product1 Product interface of class**/
public interface Product1 {
    void doSomething();
}

class Product1A implements Product1{

    @Override
    public void doSomething() {
        System.out.println("I am Product1A");
    }
}

class Product1B implements Product1{

    @Override
    public void doSomething() {
        System.out.println("I am Product1B");
    }
}


/** Product2 Product interface of class**/
public interface Product2 {
    void doSomething();
}

class Product2A implements Product1{

    @Override
    public void doSomething() {
        System.out.println("I am Product2A");
    }
}

class Product2B implements Product1{

    @Override
    public void doSomething() {
        System.out.println("I am Product2B");
    }
}

/**Abstract factory interface**/
public interface AbstractFactory {

    public Product1 createProduct1();

    public Product2 createProduct2();
}

/**A Class factory**/
public class FactoryA implements AbstractFactory{

    @Override
    public Product1 createProduct1() {
        return new Product1A();
    }

    @Override
    public Product2 createProduct2() {
        return new Product2A();
    }
}

/**B Class factory**/
public class FactoryB implements AbstractFactory{

    @Override
    public Product1 createProduct1() {
        return new Product1B();
    }

    @Override
    public Product2 createProduct2() {
        return new Product2B();
    }
}


/**Client Client call**/
public class Client {
    public static void main(String[] args) {
        new FactoryA().createProduct1().doSomething();
        new FactoryB().createProduct2().doSomething();
    }
}
  • Advantages: it is easy to increase grouping, and can greatly reduce the number of factory classes
  • Disadvantages: because of grouping, it is difficult to expand the products in the grouping. For example, adding another Product3 requires changing almost all factory classes of AbstractFactory, FactoryA and FactoryB

To sum up, no method is a panacea. Which factory mode should be used for business scenarios

References

https://www.zhihu.com/question/27125796/answer/1615074467

Re learning design mode

https://www.cnblogs.com/sunweiye/p/10815928.html

https://time.geekbang.org/column/article/197254

Tags: Design Pattern

Posted by smti on Thu, 14 Apr 2022 11:06:26 +0930