Try a new highlight theme. I prefer bright colors
Contents of article series (continuously updated) :
Chapter 1: Overview, Coupling, UML, Seven Principles, Detailed Analysis and summary (Based on Java)
Chapter two: Several implementations of singleton pattern And its destruction by reflection
【 Design Pattern 】 Chapter 3: A Simple factory, factory method pattern, abstract factory pattern 】
The Builder model is not that difficult
Chapter 5: What is a Prototype Pattern? Shallow fetch shallow copy and deep copy
Article 6: Look at the adapter pattern
[Design Pattern] Chapter 7: Understand the bridge pattern with me
[Design mode] Chapter 8: Drinking soy milk is decorator mode?
[Design Pattern] Chapter 9: Combination pattern to solve the problem of hierarchical structure
[Design mode] Chapter 10: Appearance mode, the joy of driving a small broken car
[Design mode] Chapter 11: Let’s take a look at the yuan model
[Design Mode] Chapter 12: explanation of agent mode in the ticket purchase scene
Why use factory model
We’ve already talked about the factory design pattern in Spring’s dependency injection article. Let’s start with an example of what can be done with the factory pattern.
Easy Introduction to The Spring Framework Step by step (IOC and DI)
Juejin. Cn/post / 684490…
First, we simply simulate the operation of adding an account, doing it the way we’ve always done it before, and then coming up with an improvement plan
(I) Take an example to simulate Spring IOC
(1) Previous procedures
First of all, we’re going to do our usual simulation, we’re going to start with a basic set of processes
A: the Service layer
/** * Account business layer interface */
public interface AccountService {
void addAccount(a);
}
/** * Account business layer implementation class */
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
public void addAccount(a) { accountDao.addAccount(); }}Copy the code
B: the Dao layer
/** * Account persistence layer interface */
public interface AccountDao {
void addAccount(a);
}
/** * Account persistence layer implementation class */
public class AccountDaoImpl implements AccountDao {
public void addAccount(a) {
System.out.println("User added successfully!"); }}Copy the code
C: call
Since the Maven project we created is not a Web project and we created it for simple simulation purposes, we created a Client class here to test our methods as a Client
public class Client {
public static void main(String[] args) {
AccountService as = newAccountServiceImpl(); as.addAccount(); }}Copy the code
The result is a successful user addition message printed on the screen
D) New problems
The above code, should be relatively simple and easy to think of a way to implement, but its coupling is very high, which is caused by these two lines of code, because the business layer (service) call persistence layer (DAO), At this point the business layer will rely heavily on the persistence layer interface (AccountDao) and implementation class (AccountDaoImpl).
private AccountDao accountDao = new AccountDaoImpl();
AccountService as = new AccountServiceImpl();
Copy the code
In this way, the dependency between different classes is greatly enhanced through the new object. The problem of one class will directly lead to the global problem. If we incorrectly modify the method called, or delete a class, the execution result is:
Errors occur at compile time, and we, as developers, should strive to make the program compile time without dependencies, so that it can have some necessary dependencies at run time (dependencies are impossible to eliminate completely).
Therefore, we should find ways to decouple. To decouple, there should be no direct connection between the caller and the called. Then the factory model can help us solve this problem well
(2) Factory mode improvement
A: the BeanFactory
How do you do that? Here we can configure both serivice and DAO into a configuration file (XML/Properties), read the contents of the configuration file through a class, create objects using reflection techniques, and then store them. The class that does this is our factory
Note: We use properties here, mainly for convenience. XML also involves some parsing code, which is a bit more cumbersome, but Spring uses XML as a configuration file
- Bean.properties: Write the configuration file first, and configure the service and DAO in key=value format
accountService=cn.ideal.service.impl.AccountServiceImpl
accountDao=cn.ideal.dao.impl.AccountDaoImpl
Copy the code
- BeanFactory
public class BeanFactory {
// Define a Properties object
private static Properties properties;
// Assign a value to the Properties object using a static code block
static {
try{
// instantiate the object
properties = new Properties();
// Get the stream object of the properties file
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
}catch (Exception e){
throw new ExceptionInInitializerError("Failed to initialize properties"); }}}Copy the code
Here’s a quick explanation of the code (not yet written, of course) : The first step is to read the contents of the configuration file. This is done in the classloader fashion, reading a stream file and then reading the key-value pairs from it. Since you only need to read them once, you put them in the static code block
We then continue to write a getBean method in the BeanFactory, which has two core lines of code that say:
- Find the path to the corresponding full class name through the string passed in the method argument, essentially finding the value through the key from the configuration we just fetched
- Load the Class through the Class load method and return the instantiation
public static Object getBean(String beanName){
Object bean = null;
try {
// Get value based on key
String beanPath = properties.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
Copy the code
B: Test code:
public class Client {
public static void main(String[] args) {
AccountService as = (AccountService)BeanFactory.getBean("accountService"); as.addAccount(); }}Copy the code
C: Execution effect:
When we do the same thing and remove the implementation class of the called DAO, we can see that the compile-time error has disappeared and only a run-time exception is reported, thus solving the previous problem
We should try to make the program compile time without dependencies so that it can have some necessary dependencies at run time (dependencies cannot be eliminated completely).
(3) Summary:
Why is the factory pattern used instead of the new approach?
For example, in your program, if after a period of time, you find that there is a bug in your new this object or unreasonable place, or do you want to change even a persistence layer framework, this kind of circumstance, can’t, can modify the source code, and then recompile, deployment, but if you use the factory pattern, All you have to do is rewrite the class you want to change, compile it, and put it in a file, and just modify the configuration file
I would like to share with you my personal shorthand:
New objects depend on concrete things, while not new depends on abstract things.
Break it down:
- Depending on something concrete, that’s easy to understand, you’re relying on something concrete, something concrete, something that’s relevant to you, so what’s the problem, it’s all interlocking, maybe for a point, we need to fix N things, desperate
- Relying on abstract things, what you call is not something that is immediately available, it is an abstract concept, so there is no chain reaction like the one above
Two or three factory models
After looking at the previous example, I think you have a very intuitive view of the factory pattern
To put it bluntly, factory mode uses a means to replace the new operation
In the past, when we wanted to get an instance, we had to get new, but this way the coupling was very high, and we wanted to minimize the avoidable coupling burden, so the factory pattern came into being
The factory is a connecting hub between the caller and the called, the caller and the called only contact with the factory, thus reducing the direct dependence between the two
There are three types of factory patterns: (1) simple factory pattern, (2) factory method pattern and (3) Abstract factory pattern
Let’s take it one at a time
(a) Simple factory model
(1)
Let’s take a Car example. First we have an abstract Car class
public abstract class Car {
// Any car can run
public abstract void run(a);
}
Copy the code
Then is its subclass, we first come to two, a BMW class, a Benz class (for reading convenience written pinyin named, do not imitate, not recommended)
public class BaoMa extends Car {
@Override
public void run(a) {
System.out.println("[BMW] running on the road"); }}Copy the code
public class BenChi extends Car {
@Override
public void run(a) {
System.out.println("Running on the road"); }}Copy the code
So if I want to instantiate this class, actually the original way to write it is this.
public class Test {
public static void main(String[] args) {
Car baoMa = new BaoMa();
baoMa.run();
Car benChi = newBenChi(); benChi.run(); }}Copy the code
If you use the simple factory pattern, you need to create a special factory class to instantiate objects
public class CarFactory {
public static Car createCar(String type) {
if ("BMW".equals(type)) {
return new BaoMa();
} else if ("Mercedes".equals(type)) {
return new BenChi();
} else {
return null; }}}Copy the code
To actually call, I just need to pass in a correct parameter and create the desired thing through CarFactory. How to create the desired thing does not need to worry about the caller
public class Test {
public static void main(String[] args) {
Car baoMa = CarFactory.createCar("BMW");
baoMa.run();
Car benChi = CarFactory.createCar("Mercedes"); benChi.run(); }}Copy the code
(2) the advantages and disadvantages
First, the advantages:
The advantage of the simple factory model is that the factory class contains the necessary logical judgment (for example, the determination of BMW or Benz in CarFactory). The client only needs to dynamically instantiate the desired class by passing in parameters (such as “BMW”), and the client is not responsible for directly creating the product. Remove the dependency on specific products (don’t even need to know the specific class name, I’m not responsible for creating it anyway)
But its disadvantages are also obvious:
The factory class in the simple factory mode is too heavy, which violates the principle of high aggregation. At the same time, the logic is too complicated in the case of many contents. Crucially, when I wanted to add something new, such as a Porsche, I had to change the code in the CarFactory factory class, which obviously violated the “on/off principle”.
So here he is in factory mode
(2) Factory model
(1)
It is still a car abstract class, with a BMW class and a Mercedes class as subclasses
public abstract class Car {
// Any car can run
public abstract void run(a);
}
Copy the code
public class BaoMa extends Car {
@Override
public void run(a) {
System.out.println("[BMW] running on the road"); }}Copy the code
public class BenChi extends Car {
@Override
public void run(a) {
System.out.println("Running on the road"); }}Copy the code
In the case of a simple factory class, there is a total factory class to instantiate the object, and to address its shortcomings, the factory class first needs to create a car factory interface class
public interface CarFactory {
// You can get any vehicle
Car createCar(a);
}
Copy the code
Then the BMW and Mercedes classes implement it separately, creating a corresponding BMW or Mercedes class (instantiating the BMW or Mercedes class)
public class BaoMaFactory implements CarFactory {
@Override
public Car createCar(a) {
return newBaoMa(); }}Copy the code
public class BenChiFactory implements CarFactory {
@Override
public Car createCar(a) {
return newBenChi(); }}Copy the code
When I want to obtain a car, I just need to create the factory of the car I want to obtain through polymorphism, and then create the corresponding car through the factory. For example, I can do this when I get Mercedes Benz and BMW respectively:
public class Test {
public static void main(String[] args) {
// Go to the Mercedes factory and get a Mercedes
CarFactory benChiFactory = new BenChiFactory();
// The 4S shop got a Mercedes Benz and gave it to you
Car benChi = benChiFactory.createCar();
benChi.run();
// Go to the BMW factory first to get a BMW
CarFactory baoMaFactory = new BaoMaFactory();
// The 4S shop got a BMW and gave it to youCar baoMa = baoMaFactory.createCar(); baoMa.run(); }}Copy the code
In this case, if I want to add a Porsche type Car, after creating the corresponding Porsche class (inheriting Car) and the corresponding Porsche factory class, I still need to call the above method
// Go to the Porsche factory and pick up a Porsche
CarFactory baoShiJieFactory = new BaoShiJieFactory();
// The 4S shop got a Porsche and gave it to you
Car baoShiJie = baoShiJieFactory.createCar();
baoShiJie.run();
Copy the code
(2) definition
Factory method pattern: Defines an interface for creating objects and lets subclasses decide which class to instantiate. Factory methods defer the instantiation of a class to its subclasses
Look at the structure diagram
(3) the advantages and disadvantages
Advantages:
-
The creation of an object is explicitly separated into the child factory classes and no longer needs to be considered on the client side
-
Adding new content is very convenient. You just need to add a class that you want to generate and the factory class that creates it
-
Do not violate the “open and close principle”, late maintenance, easy to expand
Disadvantages:
- The amount of code increases significantly
(3) Abstract factory model
Abstract Factory pattern is one of the more complex factory patterns, so let’s look at it directly in code
Still speaking of cars, we divide cars into two types, one is ordinary cars and the other is trucks. In the previous factory method mode, if the types of cars are constantly increased, it is bound to cause too many factories. However, for common cars, we can also look for features that can be extracted to carry out abstraction
So on this basis, we respectively set the automatic transmission and manual transmission two types, so two pairs of collocation, there are four cases (eg: automatic transmission truck, manual transmission car, etc.)
(1) Create abstract products
- First, create an abstract class for a normal car and a truck. Then define the two methods.
public abstract class CommonCar {
// All cars can, stop
abstract void parking(a);
// All cars can change gear
abstract void shiftGear(a);
}
Copy the code
public abstract class Truck {
// All cars can, stop
abstract void parking(a);
// All cars can change gear
abstract void shiftGear(a);
}
Copy the code
(2) Implement abstract products
Eg: CommonCarA stands for ordinary automatic transmission car
- Realize abstract product — Car (automatic transmission)
public class CommonCarA extends CommonCar{
@Override
void parking(a) {
System.out.println("Automatic car A, stop and put it in P.");
}
@Override
void shiftGear(a) {
System.out.println("Automatic transmission car A, convertible P N D R"); }}Copy the code
- Realize abstract product — Car (manual shift)
public class CommonCarH extends CommonCar {
@Override
void parking(a) {
System.out.println("Car H, manual, park in neutral, pull on the handbrake.");
}
@Override
void shiftGear(a) {
System.out.println("Manual shift car H, convertible gap 1, 2, 3, 4, 5 R"); }}Copy the code
- Realize abstract product — Truck (automatic transmission)
public class TruckA extends Truck {
@Override
void parking(a) {
System.out.println("Automatic truck A, stop and put it in P.");
}
@Override
void shiftGear(a) {
System.out.println("Automatic transmission truck A, transferable P N D R"); }}Copy the code
- Realize abstract product — truck (manual shift)
public class TruckH extends Truck {
@Override
void parking(a) {
System.out.println("Manual shift truck H, stop in neutral, pull on the handbrake.");
}
@Override
void shiftGear(a) {
System.out.println("Manual shift truck H, shift free 1 2 3 4 5 R"); }}Copy the code
(3) Create an abstract factory
public interface CarFactory {
// Create a normal car
CommonCar createCommonCar(a);
// Create a van
Truck createTruckCar(a);
}
Copy the code
(4) Realize abstract factory
Through the two abstract concepts of automatic and manual gear, the two factories are created, and the product objects with specific implementation classes are created
- Automatic transmission car factory category
public class AutomaticCarFactory implements CarFactory {
@Override
public CommonCarA createCommonCar(a) {
return new CommonCarA();
}
@Override
public TruckA createTruckCar(a) {
return newTruckA(); }}Copy the code
- Manual shift car factory class
public class HandShiftCarFactory implements CarFactory {
@Override
public CommonCarH createCommonCar(a) {
return new CommonCarH();
}
@Override
public TruckH createTruckCar(a) {
return newTruckH(); }}Copy the code
(5) Test it out
public class Test {
public static void main(String[] args) {
//
CarFactory automaticCarFactory = new AutomaticCarFactory();
// This is not the case
CarFactory handShiftCarFactory = new HandShiftCarFactory();
System.out.println("======= Automatic transmission car series =======");
CommonCar commonCarA = automaticCarFactory.createCommonCar();
commonCarA.parking();
commonCarA.shiftGear();
System.out.println("======= automatic truck series =======");
Truck truckA = automaticCarFactory.createTruckCar();
truckA.parking();
truckA.shiftGear();
System.out.println("======= Manual shift car series =======");
CommonCar commonCarH = handShiftCarFactory.createCommonCar();
commonCarH.parking();
commonCarH.shiftGear();
System.out.println("======= Manual transmission truck series ======="); Truck truckH = handShiftCarFactory.createTruckCar(); truckH.parking(); truckH.shiftGear(); }}Copy the code
Running results:
======= automatic transmission car series ======= automatic transmission car A, parking with P gear automatic transmission car A, convertible P N D R ======= automatic transmission truck series ======= automatic transmission truck A, parking with P gear automatic transmission truck A, P N D R ======= manual shift car series ======= manual shift car H, parking in neutral, handbrake manual shift car H, parking in neutral 1 2 3 4 5 R ======= Manual shift truck series ======= Manual shift truck H, parking in neutral, Handbrake manual shift truck H, shiftable air 1 2 3 4 5 RCopy the code
Add two concepts
- Product grade structure: The hierarchical structure of a product is its inheritance structure. For example, in the code above, CommonCar is an abstract class, and its subclasses are CommonCarA (automatic transmission car) and CommonCarH (manual transmission car). Ordinary car abstract class and specific automatic transmission or manual transmission of the car constitute a product grade structure.
- Product family: product family is a group of products produced in the same factory and located in different product grade structures. For example, in the code above, CommonCarA and TruckA are both generated by the same AutomaticCarFactory
(6) structure
Look at the schematics. Let’s go through it again
AbstractProductA and AbstractProductB are two abstract products respectively corresponding to CommonCar and Truck in the above code. Why abstractcar and Truck are abstract, because they can have two different implementations, namely automatic transmission car and automatic Truck. Manual transmission cars and manual transmission trucks
ProductA1 and ProductA2 and ProductB1 and ProductB2 are concrete implementations, representing CommonCarA and CommonCarH and TruckA and TruckH
AbstractFactory AbstractFactory AbstractFactory contains abstract methods for creating all products. ConcreteFactory1 and ConcreteFactory2 are concrete factories. This factory then creates product objects with specific implementations, meaning that clients should use different concrete factories in order to create different product objects
(7) Reflection + configuration file optimization
Abstract factory is to reduce the number of factories by means of content abstraction, and we can use it in concrete factories
CarFactory factory = new AutomaticCarFactory();
Copy the code
A specific factory only needs to appear once during initialization, which makes it easy to modify a specific factory
But the disadvantage is also very obvious, when I want to extend a, for example to add a tractor type, I need to modify the CarFactory interface, the AutomaticCarFactory class HandShiftCarFactory class, (of course, the tractor doesn’t seem to have any automatic transmission, I’m just giving an example), Also need to add tractor corresponding content
That is to say, I need to modify the original three classes in addition to the addition, which is a very significant disadvantage
And there’s another problem, if it’s declared in a lot of places
CarFactory factory = new AutomaticCarFactory();
Copy the code
If I change the factory, I’m going to have to make a lot of changes, and obviously this is a problem, so let’s use reflection to optimize
public class Test {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
// Use ClassLoader to load the properties configuration file to generate the corresponding input stream
InputStream in = Test.class.getClassLoader().getResourceAsStream("config.properties");
// Load the input stream using the properties object
properties.load(in);
// Get the value of the key
String factory = properties.getProperty("factory");
CarFactory automaticCarFactory = (CarFactory) Class.forName(factory).newInstance();
System.out.println("====== Car series =======");
CommonCar commonCarA = automaticCarFactory.createCommonCar();
commonCarA.parking();
commonCarA.shiftGear();
System.out.println("======= truck series ======="); Truck truckA = automaticCarFactory.createTruckCar(); truckA.parking(); truckA.shiftGear(); }}Copy the code
config.properties
factory=cn.ideal.factory.abstractFactory.AutomaticCarFactory
#factory=cn.ideal.factory.abstractFactory.HandShiftCarFactory
Copy the code
Running results:
======= car series ======= Automatic transmission car A, parking with P gear automatic transmission car A, convertible P N D R ======= truck series ======= automatic transmission truck A, parking with P gear automatic transmission truck A, convertible P N D RCopy the code
With reflection + configuration files we can use key-value pairs (strings) from configuration files to instantiate objects, and variables are interchangeable, which means the program moves from compile-time to run-time, increasing flexibility and eliminating the need for judgment
Going back to the previous question, if we want to add a new content now, there’s nothing to say about adding content, it’s a must, it’s an extension, but we want to turn off as much as possible for modification, now we can modify the configuration file to instantiate different specific factories, right
But there are three classes that need to be modified to add new content, and you can optimize this by removing these factories and using a simple factory that says createCommonCar(); These methods, combined with the Reflection + configuration file, can also achieve the same effect, so that if you only need to modify the configuration file and then modify the class, that is, add a createXXX method, there is no need to modify multiple content