Factory pattern of design pattern

Factory Pattern of Design Patterns

When learning Spring, I often heard about the factory pattern, and most of them have heard of BeanFactory.

described structure

  • introduce
  • Two Implementations of Simple Factory Pattern
  • Implementation of the Factory Pattern
  • Implementation of abstract factory

introduce

What exactly is the factory pattern?
Look at the following code: (comparatively cumbersome. If you don't want to see it, just skip it!!!)

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 = "";
    //Read configuration text from ruleConfigFilePath file into configText
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

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

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 = "";
    //Read configuration text from ruleConfigFilePath file into configText
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }

  private String getFileExtension(String filePath) {
    //...parse the file name to get the extension, such as rule.json, return 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 (single responsibility in the design pattern principle) and the code more clear, 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.

Simple Factory Pattern

For ease of understanding, let's try a simple example

Now we have a Shape interface with only one method in it is the drawing method

public interface Shape {
    public  void draw();
}

Then we have his implementation class Y (circle) Z (square)

public class Y implements Shape{
    @Override
    public void draw() {
        System.out.println("Y");
    }
}
public class Z implements Shape{
    @Override
    public void draw() {
        System.out.println("z");
    }
}

Now we have a factory class that is responsible for instantiating objects

public class Factory {
  public Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        }if (shapeType.equals("Y")) {
            return new Y();
        }if (shapeType.equals("Z")){
            return new Z();
        }return null;
     }
}

In this way, it is very convenient for us to obtain the object. We can directly call the factory class to obtain it. This is also the first implementation of the simple factory pattern.
But the problem is, if we need to add a class, there is too much code to change

If it can be reused, in order to save memory and object creation time, we can create and cache these Shape s in advance. When calling the getShape() function, we retrieve the Shape object from the cache and use it directly. This is somewhat similar to the combination of the singleton pattern and the simple factory pattern

import java.util.HashMap;
import java.util.Map;

public class Factory {

    private static final Map<String, Object> map = new HashMap<String, Object>();

    static {
        map.put("Y", new Y());
        map.put("Z", new Z());
    }

    public Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        Shape shape = (Shape) map.get(shapeType);
        return shape;

    }
}

We call the previous implementation method the second implementation method of the simple factory pattern.

  • For the above two factory patterns, if we need to add a class, we need to modify too many things, so does this conform to our open-closed principle? In fact, if the code is not modified frequently, and occasionally modified, it does not meet the open-closed principle, we can also accept it.
  • In addition, in the first code implementation, there is a set of if branch judgment logic, should it be replaced by polymorphism or other design patterns? In fact, it is perfectly acceptable to have if branches in the code if there are not many if branches. Applying polymorphic or design patterns to replace if branch judgment logic is not without any drawbacks. Although it improves code scalability and conforms to the open-close principle better, it also increases the number of classes at the expense of code of readability.
  • To summarize, although the simple factory mode code implementation has multiple if branch judgment logic that violates the open and close principle, it balances scalability and readability so that in most cases (for example, there is no need to add parser s frequently, nor too many Shapes) there is no problem.

factory method

What if we had to remove the if branch logic? The more classic way to deal with it is to use polymorphism: here we use humans to represent, divided into black and white

Here is a large class interface for a person
public interface human {
    public void say();
}

Then there are two his implementation classes

Black person

public class BlackHuman implements human {
    @Override
    public void say() {
        System.out.println("i am a black human");
    }
}

white people

public class WhiteHuman implements human{
    @Override
    public void say() {
        System.out.println("I am a white human");
    }
}

Then our factory class

public abstract class AbstractHumanFactory {
    public abstract human createHuman();
}

A (sub-factory) class that inherits two factory classes
black factory

public class BlackHumanFactory extends AbstractHumanFactory{
    @Override
    public human createHuman() {
        return new BlackHuman();
    }
}

white factory

public class WhiteHumanFactory extends AbstractHumanFactory{
    @Override
    public human createHuman() {
        return new WhiteHuman();
    }
}

test class

    public class Test {
        public static void main(String[] args) {
            AbstractHumanFactory abstractHumanFactory = null;
            abstractHumanFactory = new BlackHumanFactory();
            BlackHuman blackHuman = (BlackHuman) abstractHumanFactory.createHuman();
            blackHuman.say();
    
            abstractHumanFactory = new WhiteHumanFactory();
            WhiteHuman whiteHuman = (WhiteHuman) abstractHumanFactory.createHuman();
            whiteHuman.say();
        }
    }

Such an implementation method is that if we want to add a yellow race, then we only need to implement an interface of the Human class and a sub-factory that integrates the Factory, so that the decoupling is greatly decoupled.
Therefore, the factory method pattern is more in line with the open-closed principle than the simple factory pattern.

abstract factory method

In simple factories and factory methods, there is only one way of classifying classes.
But what if the classes are classified in two ways?
For example, our blacks and whites are divided into men and women respectively

  • This leads us to our abstract factory method

In this approach, we still have a large class Human

public interface Human {
    void printColor();
}

Its two abstract implementation methods (note the abstraction)
Man and WoMan

public abstract class Man implements Human {

    public void printGender() {
        System.out.println("I am a  man");
    }
    @Override
    public abstract void printColor();
}

public abstract class WoMan implements Human{

    public void printGender(){
        System.out.println("i am a woman");
    }

    @Override
    public abstract void printColor();
}

Under the classification of man and woman, of course, there are also the previous black and white

Subclasses under Man

public class BlackMan extends Man{
    @Override
    public void printColor() {
        System.out.println("i am blackColor");
    }
}

public class WhiteMan extends Man {
    @Override
    public void printColor() {
        System.out.println("I am white Color");
    }
}

Subclasses under WoMan

public class BlackWoman extends WoMan{
    @Override
    public void printColor() {
        System.out.println("I am black Color");
    }
}

public class WhiteWoman extends WoMan {
    @Override
    public void printColor() {
        System.out.println("i am white color");
    }
}

And of course our factory class

public abstract class AbstractHumanFactory {

    public abstract Man createMan();

    public abstract WoMan createWoman();

}

Negro's (sub-factory)

public class BlackHumanFactory extends AbstractHumanFactory{
    @Override
    public Man createMan() {
        return new BlackMan();
    }

    @Override
    public WoMan createWoman() {
        return new BlackWoman();
    }
}

White's (sub-factory)

public class WhiteHumanFactory extends AbstractHumanFactory{
    @Override
    public Man createMan() {
        return new WhiteMan();
    }

    @Override
    public WoMan createWoman() {
        return new WhiteWoman();
    }
}

final test

public class test {
    public static void main(String[] args) {
        AbstractHumanFactory factory = null;

        factory = new BlackHumanFactory();
        BlackMan blackMan = (BlackMan) factory.createMan();
        blackMan.printColor();
        blackMan.printGender();
        BlackWoman blackWoman = (BlackWoman) factory.createWoman();
        blackWoman.printColor();
        blackWoman.printGender();


        factory = new WhiteHumanFactory();
        WhiteMan whiteMan = (WhiteMan) factory.createMan();
        whiteMan.printColor();
        whiteMan.printGender();
        WhiteWoman whiteWoman = (WhiteWoman) factory.createWoman();
        whiteWoman.printColor();
        whiteWoman.printGender();
    }
}

This is the whole implementation process

So what did we find after reading it?

Look at the class diagram relationship of the abstract factory again

0. Here we can clearly see that if we want to get a BlackMan object, then we only need to call the BlackHumanFactory factory, we don't need to know its implementation.

1. When we want to add a new color person, we also don't have to worry about other classes, such as black and white here. To put it another way, blacks and whites are very stable here. After the test is passed, there is no need to change it. If we want to increase, we only need to implement the corresponding method and test the new implementation.

2. Although the factory mode is troublesome, the coupling degree can be greatly reduced, which is the biggest benefit of the factory mode in my opinion.

Tags: Java Design Pattern JSON

Posted by feelay on Mon, 19 Sep 2022 02:37:46 +0930