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.