23 kinds of design patterns this column allows you to grasp the construction pattern
Write in front
Why does Xiao Fu suddenly want to post a blog about 23 design patterns? Because I found that I was learning a lot of frames (when looking at the source code, not simply calling the API), you will find that there are many ingenious design methods, but in fact, you can understand what it is, but you don't know its specific scientific name, which is very embarrassing, so Xiaofu came up with the idea at this time. Moreover, these 23 design modes are required in the interview for basic knowledge of Java. To put it more simply, this is the basis Foundation, if you can't even pass the foundation, how can you promote a slightly powerful programmer? So, today's story begins
Some cases come from Li Kou's design mode in simple terms - Chong Chong!
preface
What is the design pattern? Why learn?
What are the characteristics of object-oriented? There is no doubt that anyone who has studied software engineering knows: maintainability, reusability, scalability and flexibility.
The three characteristics of object-oriented are encapsulation, inheritance and polymorphism. I won't repeat it here.
The strength of object-oriented design is that as the business becomes more and more complex, object-oriented design can still make the program structure good, while process oriented design will make the program more and more bloated.
The design pattern is the secret to keep the object-oriented design structure good. When the object-oriented design pattern is combined, we can really realize that the program becomes: good maintainability, high reuse rate, strong expansibility, flexible and changeable. Design patterns are not new to program apes, and they have heard of them more or less. Every program ape may inadvertently use or come into contact with design patterns when he knocks code late at night, but he doesn't notice it. In order to better understand the popular framework we use now, the idea of design patterns is in the source code to maintain the development of this architecture design, Therefore, it is urgent for us to learn design patterns well~
Six principles under 23 design modes
No matter what design mode, it is based on six design principles:
- Open and closed principle: a software entity such as class, module and function should be closed to modification and open to extension.
- Single responsibility principle: a class does one thing, and a class should have a reason for its modification.
- Richter substitution principle: the subclass should be able to completely replace the parent class. In other words, when using inheritance, only extend new functions without destroying the original functions of the parent class.
- Dependency Inversion Principle: details should depend on abstraction, and abstraction should not depend on details. The abstract layer is placed at the high level of program design and remains stable. The details of the program are changed by the underlying implementation layer.
- Interface separation principle: the client should not rely on interfaces it does not need. If some methods of an interface are empty implemented by the client due to redundancy during implementation, the interface should be split so that the implementation class only needs the interface methods it needs.
- Dimitri's Law: the principle of least knowledge to minimize the coupling between classes; One object should have the least understanding of other objects.
Constructive model
Factory mode factory
Design reasons
In normal coding, an object is often instantiated through new. At first glance, this approach is not bad, but in fact, it is a hard coding method, If you have used Spring DI (dependency injection), you will know that we can create when we need it and not when we don't need it. This is a soft coding method. When we create an object, it is equivalent to that the caller knows more than one class, which increases the relationship between classes and is not conducive to program decoupling. In fact, the construction process can be encapsulated, and the factory mode is used for encapsulation Object design pattern.
Simple factory mode
Take code as an example. Create an instance directly through new. For example, when we need an Apple phone, we need to know the construction method of Apple phone. When we need a Huawei phone, we need to know the construction method of Huawei phone. A better way is to have a mobile phone manufacturing factory. We just need to tell him what style of mobile phones we need, The factory will make the mobile phones we need (for example, a Qiang Bei goes to buy a mobile phone. I don't know where the mobile phone came from, you just need this mobile phone). We don't need to know how to make different brands of mobile phones. We just need to explain to the factory what we want.
MobilePhoneFactory.java
public class MobilePhoneFactory { public MobilePhone create (String type){ switch (type){ case "IPHONE":return new IPHONE(); case "HUAWEIPhone":return new HUAWEIPhone(); default: throw new IllegalArgumentException("Do not sell such mobile phones for the time being!"); } } }
Customer.java
public class Customer { public void buy(){ System.out.println("/*************Obtain the products required by users through simple factory mode**************"); MobilePhoneFactory mobilePhoneFactory = new MobilePhoneFactory(); MobilePhone iphone = mobilePhoneFactory.create("IPHONE"); MobilePhone huaweiPhone = mobilePhoneFactory.create("HUAWEIPhone"); iphone.showInfo(); huaweiPhone.showInfo(); } }
be careful
Construction process encapsulation can not only reduce the coupling degree, but also greatly reduce code duplication if the construction method of a product is very complex. For example, the production of an apple mobile phone requires a screen, battery and motherboard, so modify it as follows:
public class MobilePhoneFactory { public MobilePhone create (String type){ switch (type){ case "IPHONE": Screen screen = new Screen(); Battery battery = new Battery(); Mainboard mainboard = new Mainboard(); return new IPHONE(battery,screen,mainboard); case "HUAWEIPhone":return new HUAWEIPhone(); default: throw new IllegalArgumentException("Do not sell such mobile phones for the time being!"); } } }
The caller's code does not need to be changed at all (that is, the Customer does not need to change), and the caller does not need to build the screen, battery and motherboard to obtain the desired mobile phone every time he needs the mobile phone. No matter how complex the mobile phone production process is, it is only the factory to help us create it, which is the concrete embodiment of packaging. If the mobile phone needs to be updated and replaced with new technology, such as 4G to 5G mobile phone, it is just a branch Scientists bring the corresponding technology to the factory for parts processing, which has nothing to do with us. We don't need to pay attention to how it comes. We just need to care about what we need.
There are three factory modes:
- Simple factory mode
- Factory method model
- Abstract factory pattern
Note: the simple factory pattern is to let a factory class assume the responsibilities of all objects. The factory produces whatever the caller needs. The disadvantages can be seen from this:
- 1: If there are too many products to be produced, the factory class will be too large and assume too many responsibilities, and it will become a super class. When the process needs to be modified, you have to come to the factory class to find the specified location for processing and modification, which violates the principle of single responsibility. (one class does one thing)
- 2: When you want to produce a new product, you must add a new branch under the factory class. The open and closed principle tells us that classes should be closed to modifications and open to extensions. We hope that when adding new functions, we only need to add new classes instead of modifying existing classes, which violates the open and closed principle.
Factory method model
In order to solve the two problems of the above simple factory model, the factory method model came into being, which stipulates that each product has an exclusive factory. For example, Apple's mobile phones are produced in its own factory, and Huawei's mobile phones are produced in Huawei's factory.
Apple mobile factory:
public class IPHONEFactory { public IPHONE create (){ return new IPHONE(); } }
Huawei mobile phone factory:
public class HUAWEIFactory { public HUAWEIPhone create (){ return new HUAWEIPhone(); } }
User:
public class Customer { public void buy(){ System.out.println("/*************Obtain the products required by users through factory mode**************"); IPHONEFactory iphoneFactory = new IPHONEFactory(); HUAWEIFactory huaweiFactory = new HUAWEIFactory(); MobilePhone iphone1 = iphoneFactory.create(); MobilePhone huaweiPhone1 = huaweiFactory.create(); iphone1.showInfo(); huaweiPhone1.showInfo(); } }
Maybe some people feel wrong when they read this. Is this different from directly obtaining an instance through a new object? The factory pattern mentioned earlier is to reduce the coupling between classes and let callers deal with other classes as little as possible. Using the simple factory model, we only need to know the mobile phone factory without knowing how they are made, which greatly reduces the coupling degree. However, although the caller does not need to know how Apple phones and Huawei phones are constructed, we need to know these manufacturing factories. We need to know how many kinds of production plants there are. The coupling degree has not decreased, and even the code is bloated and redundant.
Please think quietly. The second advantage of the factory mode exists. When the construction process is very complex, the factory can also encapsulate it, and users can still use it conveniently.
public class IPHONEFactory { public IPHONE create (){ Screen screen = new Screen(); Battery battery = new Battery(); Mainboard mainboard = new Mainboard(); return new IPHONE(battery,screen,mainboard); } }
The caller does not need to know the production details, and there is no need to change the caller when the production process needs to be modified. At the same time, the factory method model solves two disadvantages of the simple factory model.
- When more and more types of products are produced, the factory class will not become a super class. Although there will be more and more factory classes, it will still maintain flexibility and will not become bloated because it becomes larger and larger. If a complex production process needs to be modified, only the construction method in the factory needs to be modified. Comply with the principle of single responsibility.
- When new products need to be produced, there is no need to change the existing factory, just add a new factory. It maintains object-oriented scalability. Comply with the principle of open and closed.
Instance scenario
Both the existing SurgicalMask and N95Mask products inherit an abstract class Mask
public abstract class Mask { } public class SurgicalMask extends Mask{ @Override public String toString() { return "SurgicalMask~"; } } public class N95Mask extends Mask{ @Override public String toString() { return "N95Mask!"; } }
Please complete the following code using the simple factory mode method:
public class MaskFactory { public Mask create(String type){ //Use the simple factory pattern to implement the logic here } }
Make it pass the test of the following clients:
public class Client { @Test public void test() { MaskFactory factory = new MaskFactory(); // Output: SurgicalMask~ System.out.println(factory.create("Surgical")); // Output: N95Mask! System.out.println(factory.create("N95")); } }
Resolution:
public class MaskFactory { public Mask create(String type){ switch (type){ case "N95":return new N95Mask(); case "Surgical":return new SurgicalMask(); default: throw new IllegalArgumentException("There is no such mask~"); } } }
How to use the factory method pattern?
Client test code:
public class Client { @Test public void test2() { SurgicalMaskFactory surgicalMaskFactory = new SurgicalMaskFactory(); //Output: SurgicalMask~ System.out.println(surgicalMaskFactory.create()); N95MaskFactory N95MaskFactory = new N95MaskFactory(); //Output: N95Mask! System.out.println(N95MaskFactory.create()); } }
Scheme:
public class N95MaskFactory { public Mask create(){ return new N95Mask(); } } public class SurgicalMaskFactory { public Mask create(){ return new SurgicalMask(); } }
Abstract Factory pattern Abstract Factory
The further optimization of the factory method pattern can be further optimized to extract the common factory interface
//Public interface create method public interface Factory { public Mask create(); }
At this time, the public interface implemented by the factory class is modified as follows:
public class N95MaskFactory implements Factory{ @Override public Mask create(){ return new N95Mask(); } } public class SurgicalMaskFactory implements Factory{ @Override public Mask create(){ return new SurgicalMask(); } }
When both implement their common interfaces, they can be agreed to be used as Factory objects:
@Test public void test3() { Factory surgicalMaskFactory = new SurgicalMaskFactory(); //Output: SurgicalMask~ System.out.println(surgicalMaskFactory.create()); Factory N95MaskFactory = new N95MaskFactory(); //Output: N95Mask! System.out.println(N95MaskFactory.create()); }
Therefore, after we have created and formulated a specific Factory class, we don't need to care about which Factory class it is. We just need to use this Factory as an abstract Factory interface. This method of abstracting factories is called abstract Factory pattern.
At this time, users only need to deal with the abstract factory, and they call the methods in the interface. When using, they don't need to know which specific factory implements these methods, which makes it very easy to replace the factory.
Examples
@Test public void test4() { Factory factory = new SurgicalMaskFactory(); Mask mask = factory.create(); mask.showInfo(); }
If we need to produce N95 masks, we only need to change one place
@Test public void test4() { Factory factory = new N95MaskFactory(); Mask mask = factory.create(); mask.showInfo(); }
When abstract factory (i.e. public interface) when there is only one abstract method, you may not understand the benefits of this design. In fact, the pattern of the abstract factory is mainly used to replace a series of methods, such as replacing the MySQL database in the program with the Oracle database. If you use the abstract method pattern, you only need to define four Abstractions: addition, deletion, modification and query in the DatabaseFactory interface of the abstract factory Method, let the two factories implement this interface respectively. When calling, we can directly use the abstract method in DatabaseFactory okk. We don't need to know what database to call or the differences between different databases. We can directly use this method to replace the program's database, and as users, we don't know what happens inside it, You can still check the data as usual.
be careful
The abstract factory mode is applicable to the horizontal expansion requirements of adding similar factories, and is not suitable for the vertical expansion of new functions. Because adding new methods to the factory will have an impact on all classes that have implemented the factory. This is also its main disadvantage.
Singleton mode (key!)
The single case mode is very common, so it is often used by the interviewer as the investigation point for the basis of school recruitment. If you have nothing to do, you can tear a single case mode. Therefore, it is absolutely right to understand it carefully as a key point.
When only one instance of an object needs to appear globally, the singleton mode can be used. Its advantages are also obvious:
- It can avoid repeated creation of objects, save space and improve efficiency
- Avoid logical errors caused by operating different instances
Let's explain the single case model from simple to deep: but in the final analysis, it's still lazy and hungry.
Hungry Han style
- Hungry Chinese: variables are initialized when declared.
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
Handwriting is strongly recommended!!!
We can see that we define the construction method as private, which ensures that other classes cannot instantiate this class (except for the reflection of the source of all evil). We must use the getInstance method to obtain the unique instance, which is very intuitive. But the hungry Chinese style has a disadvantage, that is, even if this single instance does not need to be used, it will be created immediately after the class is loaded, occupying a piece of memory and increasing the class initialization time. Just like when the computer breaks down, take out all the tools first, whether or not all the tools can be used. It's like a hungry man who will not choose food, so it's called hungry man style.
Lazy style
Hungry Han mode is the simplest way to write the singleton mode, which ensures thread safety and is simple enough for us to think of at the first time. However, with the progress of development, you will find a serious problem. If there are many initialized data in the singleton mode we designed, the data will occupy a lot of space as soon as the code runs, If we don't get the instance for a long time, we don't need the object of the singleton mode, which will cause a serious waste of space. After learning the contents of the previous two sections, we should have the idea of creating it when we need it. We don't need to do this, so the second singleton mode; Lazy style was born.
- Lazy: declare an empty variable first and initialize it only when it takes time. For example:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if (instance == null){ instance = new Singleton(); } return instance; } }
We declare an instance instance instance variable with an initial value of null. When we need this instance, we first judge whether it has been initialized. If it has not been initialized, it is similar to our previous thought. We can take it when we need it and wait when we don't need it.
Lazy type solves the problem of hungry type wasting initialization space and memory, avoids space waste and reduces class initialization time.
However, this code is not safe under multi-threaded concurrent operation. When multiple threads call getInstance method at the same time, the instance variable may be instantiated many times. For thread safety, the simplest is to add synchronization lock to the null judgment condition:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } return instance; } }
In this way, the problem of thread insecurity can be solved. At most one thread can execute the empty condition and create a new instance object at a time, so the instance will only be instantiated once. When multiple threads call the getInstance method, the synchronization method will be executed every time, which seriously affects the execution efficiency of the program. Therefore, it is better to add a layer to check whether the current instance has been created before synchronization, so you don't need to enter the synchronization method:
This is what we often call the double detection lock DCL singleton mode:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //Double detection lock DCL singleton mode if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
DCL lazy singleton ensures thread safety and complies with lazy loading. It is initialized only when it is used, and the calling efficiency is relatively high. However, this writing method may have some problems in extreme cases. Because:
instance = new Singleton()
It is not an atomic operation. It has gone through at least three steps: (why? When you compile a java program, the class file will tell you the answer qwq)
- Allocate memory space for objects
- Execute constructor initialization object
- Set instance to point to the memory address just allocated. At this time, instance= null
However, due to the impact of instruction rearrangement (when talking about JMM model in multi-threaded concurrent programming, there will be A keyword called volatile to prohibit instruction replay, which does not guarantee atomicity and visibility, or the part about shielding interrupts in the principle of operating system and computer composition can also explain the prohibition of instruction replay): thread A executes instance = new Singleton(); When I was young, The third step may be executed first (the second step has not been executed yet). At this time, thread B comes in again and finds that instance is not empty. It directly returns instance, and the returned instance is used later. Since thread A has not executed the second step, the instance is not complete at this time, and there may be some unexpected errors. Therefore, the following singleton mode is available.
DCL singleton design mode with volatile keyword added:
package com.alascanfu.SingletonModel; public class Singleton { //Any changes made to this variable are committed directly from local memory to Java main memory private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
When shall we use the lazy style and the hungry style?
-
Hungry Chinese is recommended for singleton objects that are not complex to build and will be used immediately after loading.
-
For singleton objects that take a long time to build and are not used by all users of this class, lazy type is recommended.
Q: in the double check lock singleton mode, which instructions are volatile mainly used to prevent reordering? What kind of error will result if reordering occurs?
Answer: instance = new Singleton(); Instruction rearrangement may occur. In this process, if the second instruction and the third instruction are reordered, it may cause other threads to obtain the instance object of "not null but not initialized" through the null check on the outer layer of the double check lock in advance, and a null pointer exception occurs.
But does the singleton pattern necessarily create a singleton? Reflection said: it's impossible with me!
If you haven't mastered the basic use of reflection, the following article can give you a quick start to reflection.
I won't repeat it here. Oh, let's continue
Using reflection, we can break the singleton mode. You see:
public class Test { public static void main(String[] args) { try { Singleton singleton1 = Singleton.getInstance(); Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Singleton singleton2 = declaredConstructor.newInstance(); System.out.println(singleton1 == singleton2);//false } catch (Exception e) { e.printStackTrace(); } } }
Because of the existence of reflection, when single Class has been loaded into the runtime data memory. We can break the permission modifier through reflection, and private will be useless. So here
singleton 1 == singleton 2 the result is: false
This means that there is no single instance at this time.
So how to solve reflection to break access permissions and create instances?
Let's look at the code:
public class Singleton { private volatile static Singleton instance = null; private Singleton(){ synchronized (Singleton.class){ if (instance != null){ throw new RuntimeException("Don't try to break singleton mode with reflection!!!"); } } } public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
In order to prevent reflection creation, we added a synchronization lock in the constructor. When constructing a singleton, we must wait for the current instance to be created before entering. Creating an instance through reflection is actually obtaining the constructor to create an instance through reflection. So a judgment condition is added.
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.alascanfu.SingletonModel.Test.main(Test.java:12) Caused by: java.lang.RuntimeException: Don't try to break singleton mode with reflection!!! at com.alascanfu.SingletonModel.Singleton.<init>(Singleton.java:8) ... 5 more
The editor will report an error!
However, this method still has shortcomings:
Above, we first called the getInstance method normally to create the LazyMan object, so we created the object with reflection for the second time. The judgment in the private constructor worked, and reflection failed to destroy the singleton mode. However, if the destroyer simply does not call the getInstance method first and directly creates objects with reflection, our judgment will not take effect:
public class Test { public static void main(String[] args) { try { // Singleton singleton1 = Singleton.getInstance(); Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Singleton singleton2 = declaredConstructor.newInstance(); Singleton singleton3 = declaredConstructor.newInstance(); // System.out.println(singleton1 == singleton2);//false System.out.println(singleton3 == singleton2);//false } catch (Exception e) { e.printStackTrace(); } } }
At this time, no exception will be throw n, only false will be output, so it needs to be improved.
Maintain whether to build the instance for the first time by adding a flag variable
public class Singleton { private volatile static Singleton instance = null; private static boolean flag = false; private Singleton(){ synchronized (Singleton.class){ if (flag == false){ flag = true; }else { throw new RuntimeException("Don't try to break singleton mode with reflection!!!"); } } } public static Singleton getInstance(){ if (instance == null){ synchronized (Singleton.class){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
But!!!! Reflection, he still has a way to modify the attribute with permission modifier... Ah... See the moves, so we have to modify it again:
You can create globally unique constants by enumerating:
Here you can quickly master the basic use of Java enumeration.
Java foundation - enumeration can also be used
When we use enumeration to create instances, the obtained instances point to the same physical address, which ensures that no matter how many instance variables are actually the same thing.
public enum SingletonEnum { //Enumerated instances INSTANCE; public SingletonEnum getInstance(){ return INSTANCE; } } public class Test { public static void main(String[] args) { SingletonEnum instance1 = SingletonEnum.INSTANCE; SingletonEnum instance2 = SingletonEnum.INSTANCE; System.out.println(instance1 == instance2);//true } }
Can we break the enumeration class through reflection to create a new instance?
The answer is no, because when you try newInstance, it will throw an exception!
java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.alascanfu.SingletonModel.Test.main(Test.java:14)
public class Test { public static void main(String[] args) { SingletonEnum instance1 = SingletonEnum.INSTANCE; SingletonEnum instance2 = SingletonEnum.INSTANCE; System.out.println(instance1 == instance2);//true try { Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); SingletonEnum instance3 = constructor.newInstance(); SingletonEnum instance4 = constructor.newInstance(); System.out.println(instance3 == instance4); } catch (Exception e) { e.printStackTrace(); } } }
As for why the constructor here is of this type, you can only know by referring to the source code:
Use jad to decompile to see! Not even javap!
jad.exe download link: https://pan.baidu.com/s/1K3Mmm2Grlv5x2Y_NPEfS2Q Extraction code: HHXF -- sharing from Baidu online disk super member V3
Take JAD first Exe is placed in the bin directory of the jdk so that it can be used
Then through the instruction JAD - sjava XXXXX Class to decompile
In this way, you will know why the constructor of enumeration is the above type.
After learning the singleton mode, you will learn it at home~
Builder mode builder
The builder pattern is used to create objects with stable processes but variable configurations.
The builder mode is mainly used to stabilize the construction process and build scenes of different objects through different configurations.
The definition given in design pattern is to separate a complex construction from its representation, so that the same construction process can create different representations.
Now the "builder commander" mode is rarely used. The current builder mode mainly generates different configurations through chain calls.
When it is necessary to make a cup of pearl milk tea, its production process is stable. In addition to knowing the type and specification, whether to add ice and accessories are optional. The builder's mode is as follows:
package com.alascanfu.BuilderModel; public class MilkTea { private final String type; private final String size; private final Boolean pearl; private final Boolean ice; private MilkTea(Builder builder){ this.type = builder.type; this.size = builder.size; this.pearl = builder.pearl; this.ice = builder.ice; } public static class Builder{ private final String type; private String size = "Medium cup"; private Boolean pearl = true; private Boolean ice = false; public Builder(String type){ this.type = type; } public Builder size(String size){ this.size = size; return this; } public Builder pearl(Boolean pearl){ this.pearl = pearl; return this; } public Builder ice(Boolean ice){ this.ice = ice; return this; } public MilkTea build(){ return new MilkTea(this); } } public String getType() { return type; } public String getSize() { return size; } public Boolean getPearl() { return pearl; } public Boolean getIce() { return ice; } }
As you can see, we set the construction method of MilkTea to private, so the external cannot build the MilkTea instance through new, but only through Builder. The properties that must be configured are passed in through the Builder's construction method, and the optional properties are passed in through the Builder's chain call method. If not configured, the default configuration will be used, that is, medium cup, pearl and no ice. Different milk teas can be made according to different configurations:
Customer needs:
package com.alascanfu.BuilderModel; public class User { public static void main(String[] args) { MilkTea milkTea = new MilkTea.Builder("Original flavor").build(); show(milkTea); MilkTea chocolate = new MilkTea.Builder("Chocolate flavor").size("Big cup").ice(true).pearl(true).build(); show(chocolate); MilkTea strawberry = new MilkTea.Builder("Strawberry flavor").size("Small cup").ice(false).pearl(false).build(); show(strawberry); } private static void show(MilkTea milkTea) { String pearl; if (milkTea.getPearl()) { pearl = "Add pearl"; } else{ pearl = "No pearl"; } String ice; if (milkTea.getIce()) { ice = "Add ice"; } else { ice = "neat"; } System.out.println("A copy" + milkTea.getSize() + "," + pearl + "," + ice + "of" + milkTea.getType() + "tea with milk"); } }
Prototype pattern
Prototype pattern: specify the type of objects to be created with prototype instances, and create new objects by copying these prototypes.
Take a chestnut~
Or milk tea. For example, Xiaofu likes to drink plain milk tea without ice, My roommate was a patient with choice difficulty. He said he would have the same one as me
public class MilkTea { public String type; public boolean ice; } public class Customer { public void order(){ MilkTea milkTeaOfAlascanfu = new MilkTea(); milkTeaOfAlascanfu.type = "Original flavor"; milkTeaOfAlascanfu.ice = false; MilkTea yourMilkTea = milkTeaOfAlascanfu; } }
It seems that there is no problem, but does he get a new cup of milk tea?
Of course not, because Java only passes the address of the basic type object in its assignment. After this assignment, yourMilkTea still points to Xiaofu's cup of milk tea, and there will be no new milk tea.
public class Customer { public void order(){ MilkTea milkTeaOfAlascanfu = new MilkTea(); milkTeaOfAlascanfu.type = "Original flavor"; milkTeaOfAlascanfu.ice = false; MilkTea yourMilkTea = new MilkTea(); yourMilkTea.type = "Original flavor"; yourMilkTea.ice = false; } }
This is as like as two peas, which are the same as the milk tea.
If the number of peers is large, the code will be written many times, which is not only redundant but also a waste of space.
Xiao Fu is tired of drinking and wants to change his taste. He wants to be the same as me. Does everyone need to modify it? It's time to go back to Europe~
Mass modification is undoubtedly a very ugly practice, which is why we need the clone method!
Using prototype mode, add clone method in MilkTea:
public class MilkTea { public String type; public boolean ice; public MilkTea clone(){ MilkTea milkTea = new MilkTea(); milkTea.ice = this.ice; milkTea.type = this.type; return milkTea; } }
The user's order placement method is also modified, and the clone method written by us is called:
public class Customer { public void order(){ MilkTea milkTeaOfAlascanfu = new MilkTea(); milkTeaOfAlascanfu.type = "Original flavor"; milkTeaOfAlascanfu.ice = false; MilkTea clone1 = milkTeaOfAlascanfu.clone(); } }
This is the prototype pattern. There is a syntax sugar in Java, so we don't need to handwritten clone method. This syntax sugar is the clonable interface. We just need to make the class to be copied implement this interface.
public class MilkTea implements Cloneable{ public String type; public boolean ice; @Override public MilkTea clone(){ MilkTea milkTea = new MilkTea(); milkTea.ice = this.ice; milkTea.type = this.type; return milkTea; } }
be careful
Note that Java's own clone method is a shallow copy, that is, when calling the clone method of the current object, only the parameters of the basic data type will be copied, but the non basic type objects will not be copied. Instead, the method of passing references will continue to be used. If you need to implement deep copy, you must manually modify the clone method~
Constructive model - Summary
- Factory method pattern: establish a factory for each type of object, hand over the object to the factory to create, and the client only deals with the factory.
- Abstract factory mode: abstract interfaces are extracted for each type of factory, making it very easy to add and replace factories.
- Builder mode: used to create objects with stable construction process. Different builders can define different configurations.
- Singleton mode: the same object is used globally, which is divided into hungry and lazy. The lazy type has two implementation methods: double check lock and internal class. (there are also singletons of enumeration types)
- Prototype pattern: define clone method for a class, which makes it easier to create the same object.
Write it at the back
This summary is the first of 23 design patterns: constructive patterns
There are five design modes under this category:
Factory method pattern, abstract factory pattern, singleton pattern, builder pattern, prototype pattern.
The remaining design patterns will be updated one after another, which is also the basis of the framework~
If you can, please do it.
last
Make progress every day and harvest every day
May you succeed in your career and learn
If you feel good, don't forget to click three times~