Design pattern
1. Singleton mode
1.1 scenario
Many times, the whole system only needs to have a global object, which is conducive to coordinating the overall behavior of the system. For example, we often use global cache objects.
The idea of implementing singleton pattern is that a class can return an object, a reference (always the same) and a method to obtain the instance (it must be a static method, usually using the name getInstance); when we call this method, if the reference held by the class is not empty, we return this reference; if the reference held by the class is empty, we create an instance of the class and give the instance reference to the reference held by the class; at the same time, we also define the constructor of the class as a private method, so that other generations can Code cannot instantiate the object of the class by calling the constructor of the class. Only the static method provided by the class can get the unique instance of the class.
Singleton mode must be used with caution in multithreaded applications. If two threads call the creation method at the same time when the unique instance has not been created, they do not detect the existence of the unique instance at the same time, so they create an instance at the same time. In this way, two instances are constructed, which violates the principle of uniqueness of instances in singleton mode. The solution to this problem is to provide a mutex for variables indicating whether the class has been instantiated (although this will reduce efficiency).
1.2 eight methods
1.2. 1. Hungry Han style
Hungry Chinese means that the global singleton instance is built when the class is loaded. Whether this instance is used or not, it exists in memory.
public class SingletonNoLazy { private static final SingletonNoLazy INSTANCE = new SingletonNoLazy(); /** * Static code block */ /*static { INSTANCE = new Singleton(); }*/ // Private constructor // default public constructor private SingletonNoLazy() {}; public static SingletonNoLazy getInstance() { return INSTANCE; } }
Advantages: the writing method is simple, and the instantiation is completed when the class is loaded. Avoid thread synchronization problems.
Disadvantages: the class is instantiated when it is loaded. If the class object is not used, it will cause memory waste.
Static variables and static code block loading order:
Static variables and static code blocks in Java are executed when the class is loaded. When instantiating an object, declare and instantiate the variables first, and then execute the constructor. If the subclass inherits from the parent class, the static variables and static code blocks of the parent class are executed first, and then the static variables and static code blocks of the subclass are executed. Similarly, non static code blocks and constructors are then executed in the parent and child classes.
Note: (static) variables and (static) code blocks also have execution order, which is consistent with the order of code writing. In (static) code blocks, (static) variables can be used, but the used (static) variables must be declared in front of (static) code blocks.
1.2. 2 lazy style
Lazy means that the global singleton instance is built when the class is loaded.
1.2. 2.1 unsafe lazy
public class SingletonLazyNoSafe { private static SingletonLazyNoSafe INSTANCE; private SingletonLazyNoSafe() {} public static SingletonLazyNoSafe getInstance() { if(INSTANCE == null) { INSTANCE = new SingletonLazyNoSafe(); } return INSTANCE; } }
Advantages: lazy loading, no waste of memory.
Disadvantages: multithreading is not a single instance. There are errors, which cannot be used in practice.
1.2. 2.2 safe lazy
-
Method lock
public class SingletonLazySafe { private static volatile SingletonLazySafe INSTANCE; private SingletonLazySafe() {} public synchronized static SingletonLazySafe getInstance() { if(INSTANCE == null) { INSTANCE = new SingletonLazySafe(); } } return INSTANCE; } }
Advantages: it can be used without error.
Disadvantages: the method adds a syn lock, but multiple threads acquire it at the same time, which will block and be too inefficient.
-
Double lock
public class SingletonLazySafe { private static volatile SingletonLazySafe INSTANCE; private SingletonLazySafe() {} /** * 1. Method adds a syn lock, but multiple threads acquire it at the same time, which will block and be inefficient * 2. Double lock mode */ public static SingletonLazySafe getInstance() { if(INSTANCE == null) { synchronized (SingletonLazySafe.class) { if(INSTANCE == null) { INSTANCE = new SingletonLazySafe(); } } } return INSTANCE; } }
Attention should be paid to double locking: the volatile keyword should be added to static variables to prevent reordering during virtual machine compilation.
volatile has two functions:
- Visibility between threads
- Prevent instruction rearrangement
The function here is to prevent instruction rearrangement.
Each different instantiated object needs a piece of memory in the jvm to store. The new object is divided into three steps:
- Allocate memory space
- Initialize object
- Point the object to the memory space just allocated (if the address value is returned, unlock it)
During the jvm instruction optimization, steps 2 and 3 will be swapped. For example, thread 1 enters the new action after two layers of null judgment, and returns the address value when the object has not been initialized. Thread 2 directly returns the object when the first null judgment is made because the object is not empty. However, when thread 2 intended to use the Singleton instance, it found that it was not initialized, and an error occurred.
-
Static inner class
public class SingletonLazySafe2 { private SingletonLazyNoSafe() {} static class SingletonHolder { private static final SingletonLazySafe2 INSTANCE = new SingletonLazySafe2(); } public static SingletonLazySafe2 getInstance() { return SingletonHolder.INSTANCE; }
Conclusion: when loading a class, its internal classes will not be loaded at the same time. A class is loaded when and only when one of its static members (static fields, constructors, static methods, etc.) is called.
-
Enumeration class
public class SingletonByEnum { private SingletonByEnum() {} private enum SingletonEnum { /** * example */ INSTANCE; private final SingletonByEnum instance; private SingletonEnum() { instance = new SingletonByEnum(); } private SingletonByEnum getInstance() { return instance; } } public static SingletonByEnum getInstance() { return SingletonEnum.INSTANCE.getInstance(); } }
Advantages: except enumeration, other methods will destroy the singleton by reflection, which is to generate a new object by calling the constructor
S: Therefore, if we want to prevent single instance destruction, we can judge in the construction method. If there are existing instances, we can prevent the generation of new instances.
private SingletonObject1(){ if (instance != null){ throw new RuntimeException("Instance already exists, please pass getInstance()Method acquisition"); } }
2. Factory mode
The following three factory patterns belong to creation patterns in the classification of design patterns, and the three patterns are abstracted step by step from top to bottom.
Factory mode is one of the most important creation modes. The main function of factory pattern is to help us instantiate objects. The reason why the name contains the word factory mode is that the instantiation process of objects is implemented through factories, which replace the new operation with factories.
Advantages of factory mode:
- It can make the code structure clear and effectively encapsulate changes. In programming, the instantiation of product classes is sometimes complex and changeable. The instantiation of products is encapsulated through the factory mode, so that the caller does not need to care about the instantiation process of products at all, and can get the products he wants by relying on the factory.
- Block specific product classes from callers. If the factory mode is used, the caller only cares about the product interface. As for the specific implementation, the caller doesn't need to care at all. Even if the specific implementation is changed, it has no impact on the caller.
- Reduce coupling. The instantiation of product classes is usually very complex. It needs to rely on many classes, and these classes do not need to be known to the caller at all. If the factory method is used, all we need to do is instantiate the product class and give it to the caller for use. The classes that the product depends on are transparent to the caller
Applicable scenarios:
Whether it is simple factory pattern, factory method pattern or abstract factory pattern, they have similar characteristics, so their applicable scenarios are also similar.
First, as a creation class pattern, factory method pattern can be used wherever complex objects need to be generated. One thing to note is that complex objects are suitable for factory mode, while simple objects, especially objects that can be created only through new, do not need factory mode. If you use the factory pattern, you need to introduce a factory class, which will increase the complexity of the system.
Secondly, the factory mode is a typical decoupling mode, and the performance of Demeter's law is particularly obvious in the factory mode. If the caller needs to add dependencies when assembling products by himself, the factory mode can be considered. The coupling between objects will be greatly reduced.
Thirdly, because the factory pattern relies on abstract architecture, it assigns the task of instantiating products to the implementation class, which has good scalability. In other words, when the system needs better scalability, the factory mode can be considered, and different products can be assembled in different implementation factories.
2.1 simple factory mode
Simple factory mode is really called simple factory mode because of its simplicity.
The simple factory pattern consists of three roles (elements):
- Factory: the factory class, the core part of the simple factory model, is responsible for realizing the internal logic of creating all products; Factory classes can be called directly by the outside world to create the required objects
- Product: abstract class product. It is the parent class of all objects created by the factory class and encapsulates the public methods of various product objects. Its introduction will improve the flexibility of the system, so that only one general factory method needs to be defined in the factory class, because all the created specific product objects are its subclass objects
- Concrete product: concrete product, which is the creation target of the simple factory pattern. All created objects act as instances of a specific class of this role. It implements the abstract methods declared in the abstract product
Existing problems:
When we need to add a calculation, such as square. At this time, we need to define a class to inherit the Operation class, which implements the square code. In addition, we need to modify the code of OperationFactory class and add a case. This is obviously against the opening and closing principle. It can be imagined that the factory is very passive for the addition of new products. Our example is the simplest case. In practical application, it is likely that the product is a multi-level tree structure. Simple factories may not be applicable.
Summary:
The factory class is the key to the whole simple factory pattern. It contains the necessary logical judgment to determine which specific class object should be created according to the information given by the outside world. By using the factory class, the outside world can get rid of the embarrassing situation of directly creating specific product objects, and only need to be responsible for the "consumption" objects. It doesn't matter how these objects are created and organized. Clarify their respective responsibilities and rights, which is conducive to the optimization of the whole software architecture.
However, because the factory class centralizes the creation logic of all instances, it violates the principle of high cohesion responsibility allocation and centralizes all the creation logic into one factory class; The classes it can create can only be considered in advance. If you need to add a new class, you need to change the factory class.
When there are more and more specific product classes in the system, there may be a demand for factory classes to create different instances according to different conditions. This judgment of conditions and specific product types are staggered, which is difficult to avoid the spread of module functions and is very unfavorable to the maintenance and expansion of the system;
In order to solve these shortcomings, there is a factory method pattern.
2.2 factory method mode
Summary:
Factory method pattern is a further abstraction and generalization of simple factory pattern.
Due to the use of object-oriented polymorphism, the factory method pattern not only maintains the advantages of simple factory pattern, but also overcomes its disadvantages.
In the factory method pattern, the core factory class is no longer responsible for the creation of all products, but delegates the specific creation work to the subclass. This core class is only responsible for giving the interface that a specific factory must implement, not the details that the product class is instantiated, which makes the factory method pattern allow the system to introduce new products without modifying the factory role.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-uxjyggaa-1639922341416) (typro images / image-20211213214550992. PNG)]
advantage:
- A caller wants to create an object, just know its name.
- High scalability. If you want to add a product, you can only extend a factory class.
- Mask the specific implementation of the product, and the caller only cares about the product interface.
Disadvantages:
Each time you add a product, you need to add a specific class and object implementation factory, so that the number of classes in the system increases exponentially, which not only increases the complexity of the system to a certain extent, but also increases the dependence of the specific classes of the system. This is not a good thing.
2.3 abstract factory mode
The factory method mode solves the problem of too heavy responsibilities of factory classes in the simple factory mode by introducing the factory hierarchical structure. However, since each factory in the factory method mode produces only one kind of products, there may be a large number of factory classes in the system, which is bound to increase the overhead of the system. At this time, we can consider forming some related products into a "product family", which is uniformly produced by the same factory, which is the basic idea of the abstract factory model.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ev4my0yx-163992234141418) (typro images / image-20211213214514033. PNG)]
3. Template method mode
3.1 what is it
The template method pattern is the behavior pattern of the class. Prepare an abstract class, implement some logic in the form of concrete methods and concrete constructors, and then declare some abstract methods to force subclasses to implement the remaining logic. Different subclasses can implement these abstract methods in different ways, so as to have different implementations of the remaining logic. This is the purpose of the template method pattern.
The template method pattern is based on "inheritance";
3.2 what has been solved
- Improve code reusability: put the same part of the code in the abstract parent class, and put different code in different subclasses
- Realize the reverse control: call the operation of its subclasses through a parent class, extend different behaviors through the specific implementation of subclasses, realize the reverse control and comply with the "opening and closing principle"
3.3 UML class diagram
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-vyzhxg97-163992234141418) (typro images / image-20211215134543307. PNG)]
3.4 examples
UML diagram is an application of a template method pattern for exporting csv format data.
[the external chain picture transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-a38u3fo9-163922341419) (typro images / image-20211215134834072. PNG)]
4. Builder mode
4.1 what is it
Separate the construction of a complex object from its representation, so that the same construction process can create different representations.
Scenario: when the number of constructor parameters of a class exceeds 4, and some of these parameters are optional parameters, consider using the builder mode.
4.2 what has been solved
When a class has more than four constructor parameters and some of these parameters are optional, we usually have two ways to build its object. For example, we now have the following Computer class, where cpu and ram are mandatory parameters and the other three are optional parameters. How do we construct instances of this class? There are usually two common methods:
- Collapsing constructor pattern
public class Computer { private String cpu;//must private String ram;//must private int usbCount;//Optional private String keyboard;//Optional private String display;//Optional } public class Computer { ... public Computer(String cpu, String ram) { this(cpu, ram, 0); } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "Logitech keyboard"); } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "Samsung display"); } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } }
- Java bean pattern
So what are the disadvantages of these two methods?
The first is inconvenient to use and read. You can imagine that when you want to call the constructor of a class, you first have to decide which one to use, and then there are a bunch of parameters. If there are many and the same types of these parameters, you have to find out the meaning of these parameters, which is easy to be confused... Who knows the sour Shuang.
In the second way, the state of the object is easy to change during the construction process, resulting in errors. Because the properties in that class are set step by step, it is easy to make mistakes.
In order to solve these two pain points, the builder pattern was born.
public class ComputerByBuilder { /** * must */ private String cpu; private String ram; /** * Optional */ private Integer usbCount; private String keyboard; private String display; private ComputerByBuilder(Builder builder) { this.cpu = builder.cpu; this.ram = builder.ram; this.usbCount = builder.usbCount; this.keyboard = builder.keyboard; this.display = builder.display; } public static class Builder { /** * must */ private String cpu; private String ram; /** * Optional */ private Integer usbCount; private String keyboard; private String display; public Builder(String cup,String ram) { this.cpu=cup; this.ram=ram; } public Builder setKeyBoard(String keyBoard) { this.keyboard = keyBoard; return this; } public Builder setUsbCount(int usbCount) { this.usbCount = usbCount; return this; } public Builder setKeyboard(String keyboard) { this.keyboard = keyboard; return this; } public Builder setDisplay(String display) { this.display = display; return this; } public ComputerByBuilder build(){ return new ComputerByBuilder(this); } } } // The private parameter construction method is defined in Computer, and the public internal static class builder is defined. The builder constructs the public constructor with necessary parameters and unnecessarily through the set method ComputerByBuilder builder = new ComputerByBuilder.Builder("intel", "Samsung 980").setDisplay("SKYWORTH").build();
5. Adapter mode
6. Observer mode
6.1 what is it
Observer mode is the behavior mode of an object, which is also called publish / subscribe mode, model / view mode, source / listener mode or dependencies mode. The observer pattern defines a one to many dependency that allows multiple observer objects to listen to a topic object at the same time. When the subject object changes in state, it will notify all observer objects so that they can update themselves automatically.
In Java, Observer is an Observer, which exists in the form of interface and is implemented by a specific Observer; Observable is the observed, also known as observable. It exists in the form of a class and inherits through a specific topic.
6.2 structure
- Observable: observable object, also known as observable object. By saving the references of all observer objects in an aggregate (the source code is an array), the notification of observer objects is realized. The subclass that inherits observable is called Subject.
- Concrete subject role: inherits Observable. You can use the methods of adding and deleting observer objects of the parent class through inheritance. When the internal state of a specific subject changes, the setting changes and a notice is sent to all registered observers.
- Observer: observer object. Define an interface for all specific observers to update themselves when notified by the topic. This interface is called the update interface.
- Concrete observer role: stores a state that is self consistent with the state of the subject. If necessary, the specific observer role can maintain a reference to a specific topic object.
6.3 source code
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ifaq4crc-163922341420) (typro images / image-20211219214803282. PNG)]
- MsgSubject, as a subclass that inherits Observable, acts as the observed object, that is, the topic. The parent class implements some management of observer objects. It should be noted that before notifyObservers(), you need to call setChanged() method to change the state of the observed object before the observer object can update().
- UserObserver, as an implementation class that implements Observer, acts as a specific Observer object. There is only one method, update(), which executes the update() method after the Observer object notifies.
6.4 examples
/** * Specific theme */ public class MsgSubject extends Observable { public void change(String msg) { //Object state has been changed this.setChanged(); this.notifyObservers(msg); } } /** * Specific observer */ public class UserObserver implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("object "+ o +" The message received is:"+ arg); } } //test @Test void tt8() { MsgSubject msgSubject = new MsgSubject(); Observer userObserver = new UserObserver(); Observer userObserver2 = new UserObserver(); Observer userObserver3 = new UserObserver(); msgSubject.addObserver(userObserver); msgSubject.addObserver(userObserver2); msgSubject.addObserver(userObserver3); msgSubject.change("The first er A system message!"); }