First, design mode

1.1 What are the design patterns?

  1. Design pattern is a series of routines to solve specific problems. It is a summary of the code design experience of predecessors. It has a certain universality and can be used repeatedly. The goal is to improve code reusability, code readability, and code reliability.
  2. The essence of design pattern is the practical application of object-oriented design principles, and it is a full understanding of the encapsulation, inheritance and polymorphism of classes, as well as the association and combination of classes.

1.2 Why use design patterns?

The requirements of the project are always changing, and in response to this change, our code can be easily decoupled and extended.

1.3 Types of design patterns

  • Creation pattern

The creation pattern focuses on how objects are created, and its main feature is the separation of object creation from usage. This reduces the coupling of the system and eliminates the need for users to focus on object creation details.

  • Structural mode

The structural pattern describes how classes or objects can be arranged into a larger structure. It is divided into class structure pattern, which uses inheritance mechanism to organize interfaces and classes, and object structure pattern, which uses composition or aggregation to combine objects.

  • Behavioral pattern

The behavior pattern is used to describe the complex flow control of the program at runtime, that is, to describe how multiple classes or objects cooperate to accomplish tasks that a single object cannot accomplish alone. It involves the assignment of responsibilities between algorithms and objects. It is divided into class behavior pattern and object behavior pattern. The former uses inheritance mechanism to allocate behavior between classes, and the latter uses composition or aggregation to allocate behavior between objects.

Creation pattern Structural mode Behavioral pattern
Singleton, Abstract Factory, Builder, Factory, prototype Adapter mode, Bridge mode, decorative mode, Composite mode, appearance mode, share mode, proxy mode Template method pattern, command pattern, iterator pattern, Observer pattern, Mediator pattern, memo pattern, interpreter pattern, state pattern, Policy pattern, Responsibility chain pattern (responsibility chain pattern), Visitor pattern

Two, six design principles of object-oriented design

2.1 Open and close principle

A software entity such as a class, module, or function should be open for extension and closed for modification

  • Interpretation of the
  1. Build frameworks with abstractions and extend details with implementations;
  2. New requirements should not be implemented by modifying existing classes, but rather by implementing preabstracted interfaces (or concrete classes inheriting abstract classes).
  • advantages
  1. Can not change the original code under the premise of the program extension function, increase the program scalability;
  2. It also reduces the maintenance cost of the program.

2.2 Principle of single responsibility

A class is allowed to have only one responsibility, that is, only one reason for the change of that class.

  • Interpretation of the
  1. Changes in class responsibilities are often the cause of class changes: that is, if a class has multiple responsibilities, there will be multiple reasons for the class to change, which makes the maintenance of the class difficult;

  2. Often in software development, as the requirements continue to increase, it is possible to add some responsibilities to the original class that do not belong to it, thus violating the single responsibility principle. If we find that the current class has more than one responsibility, we should separate out responsibilities that are not really part of the class.

  3. Not only classes, but also functions (methods) follow the single responsibility principle: a function (method) does only one thing. If you find a function (method) with different tasks, you need to separate out the different tasks in the form of another function (method).

  • advantages
  1. Improve code readability and substantially reduce the risk of program errors;
  2. It is helpful to track the bug and reduce the maintenance cost of the program.

2.3 Dependency inversion principle

  1. Rely on abstraction rather than implementation;
  2. Abstraction should not depend on details; Details should depend on abstractions;
  3. High-level modules cannot depend on low-level modules; both should depend on abstractions.
  • Interpretation of the
  1. Programming for interfaces, not implementations;
  2. Try not to derive from concrete classes, but to inherit abstract classes or implement interfaces;
  3. The division of high-level module and low-level module can be divided according to the level of decision-making ability. The business layer is naturally placed in the upper module, and the logical layer and data layer are naturally categorized as the bottom layer.
  • advantages
  1. Through abstraction to build a framework, the establishment of class and class correlation, to reduce the coupling between classes;
  2. The system built by abstraction is more stable, more expansible and easier to maintain than the system built by concrete implementation.
  • Richter’s substitution principle

A subclass can extend the functionality of the parent class, but cannot change the functionality of the parent class. That is, when a subclass inherits from a parent class, it should not override the methods of the parent class, except by adding new methods to accomplish new functions.

2.4 Interface Isolation Rules

Multiple specific client interfaces are better than one universal master interface.

  • Interpretation of the
  1. A client should not rely on interfaces it does not need to implement;
  2. Instead of creating a large and bloated interface, you should refine the interface as much as possible and use as few methods as possible.

Note: The granularity of interfaces should not be too small. If the size is too small, the number of interfaces is too large, which complicates the design.

  • advantages

Avoid the method that the same interface contains different kinds of responsibilities, and the interface responsibility division is more clear, in line with the idea of high cohesion and low coupling.

2.5 Demeter’s Rule (Least Know Principle)

An object should touch as few objects as possible, that is, only those that really need to be touched.

  • Interpretation of the

A class should communicate only with classes in its member variables, method inputs, and return parameters, and should not introduce other classes (indirect communication).

  • advantages

It can well reduce the coupling between classes, reduce the degree of association between classes, and make the collaboration between classes more direct.

2.6 Principles of composite polymerization reuse

All references to a base class must be able to use objects of its subclasses transparently, that is, subclass objects can replace their parent class objects without the program executing the same.

Interpretation –

In the inheritance system, a subclass can add its own unique methods and implement the abstract methods of the parent class, but cannot override the non-abstract methods of the parent class, otherwise the inheritance relationship is not a correct inheritance relationship.

  • advantages

It can check the correctness of inheritance and restrain the overflow of inheritance in use.

The concept of singleton pattern

3.1 What is the singleton pattern?

The singleton pattern creates a globally unique object that is instantiated only once during program execution. A bit like Java static variables, but the singleton pattern is superior to static variables:

  1. Static variables are used when the program startsJVMWill be loaded, if not used, will cause a lot of waste of resources;
  2. The singleton pattern enables lazy loading and the ability to create instances only when they are used.

Many of the tool classes in the development tool library apply the singleton pattern, proportional thread pool, cache, log object, etc., which only need to create one object. Creating multiple instances may cause unpredictable problems, such as waste of resources, inconsistent result processing, etc.

3.2 Why use singleton mode?

Singletons belong to the first of the three major categories of design patterns — creative patterns, which are related to the creation of objects. In other words, this pattern tries to control the number of objects created while creating them. Yes, only one instance can be created, no more.

👉 So why should we control the number of objects created?

  1. In some scenarios, the singleton mode is not used. As a result, multiple system states are not synchronized at the same time, and users cannot determine the current state.
  2. By controlling the number of objects created, you can save system resource overhead (like threads, database connections, and so on);
  3. Global data sharing.

3.3 The implementation of singleton

  1. Statically instantiate objects;
  2. Privatize constructors, disallowing instance creation through constructors;
  3. Provides a public static method that returns a unique instance.

4. Hungrier mode

When static properties are defined, the object is instantiated directly

4.1 the sample

public class HungryMode {

    /** * uses static variables to store unique instances */
    private static final HungryMode instance = new HungryMode();

    /** * privatize constructor */
    private HungryMode(a){
        // There can be many operations inside
    }

    /** * provides a public access to instance *@return* /
    public static HungryMode getInstance(a){
        returninstance; }}Copy the code

4.2 Advantages of hungry mode

The use of the static keyword guarantees thread-safety at the JVM level by ensuring that all writes to the variable are completed when the variable is referenced.

4.3 Disadvantages of hangry mode

Lazy loading cannot be implemented, resulting in wasted space: if an analogy is large, we load the class at initialization, but we do not use the class for a long time, resulting in wasted memory space.

The singleton class is initialized and loaded only when the getInstance() method is used. So here it is: the slacker.

5. Slacker mode

Lazy mode is a lazy mode that does not create an instance when the program is initialized, but only when the instance is used, so lazy mode solves the space waste caused by hungry mode.

5.1 General implementation of slacker mode

public class LazyMode {
    /** * Undefined instance */ when defining a static variable
    private static LazyMode instance;

    /** * privatize constructor */
    private LazyMode(a){
        // There can be many operations inside
    }
    /** * provides a public access to instance *@return* /
    public static LazyMode getInstance(a){
        // If the instance is empty, instantiate the object
        if (instance == null) {
            instance = new LazyMode();
        }
        returninstance; }}Copy the code

However, this implementation is not secure in the case of multi-threading, and multiple instances may occur:

if (instance == null) {
    instance = new LazyMode();
}
Copy the code

Suppose that two threads enter the above code at the same time. Since there is no resource protection, all instances that can be judged by both threads are empty and will be initialized. Therefore, there will be multiple instances.

5.2 Optimization of slacker mode

By adding the synchronized keyword to the getInstance() method, we can solve the problem of multiple instances by making it a protected resource.

public class LazyModeSynchronized {
    /** * Undefined instance */ when defining a static variable
    private static LazyModeSynchronized instance;
    /** * privatize constructor */
    private LazyModeSynchronized(a){
        // There can be many operations inside
    }
    /** * provides a public access to instance *@return* /
    public synchronized static LazyModeSynchronized getInstance(a){
        /** * Add a class lock, which affects the performance, and serialize the code. ** * Most of our code blocks are read operations, in which case the code thread is safe ** /
        if (instance == null) {
            instance = new LazyModeSynchronized();
        }
        returninstance; }}Copy the code

5.3 Advantages of slacker mode

Realize lazy loading, save memory space.

5.4 Disadvantages of slacker mode

  1. Without locking, threads are unsafe and multiple instances may occur.
  2. In the case of locking, it will serialize the program and cause serious performance problems of the system.

For getInstance(), most operations are read operations. Reads are thread-safe, so we don’t have to make every thread hold a lock in order to call the method. We need to adjust the locking problem. This has led to a new implementation pattern: the double-checked lock pattern.

Double check lock mode

6.1 General implementation of double-checked lock mode

public class DoubleCheckLockMode {

    private static DoubleCheckLockMode instance;

    /** * privatize constructor */
    private DoubleCheckLockMode(a){}/** * provides a public access to instance *@return* /
    public static DoubleCheckLockMode getInstance(a){
        // If this is empty, return the instance directly without entering the lock grab phase
        if (instance == null) {
            synchronized (DoubleCheckLockMode.class) {
                // Check again if the lock is empty
                if (instance == null) {
                    instance = newDoubleCheckLockMode(); }}}returninstance; }}Copy the code

Double-checked locking solves singleton, performance, and thread-safety problems, but there are also problems with this approach: in multithreaded situations, null-pointer problems can occur because the JVM performs optimization and instruction reordering operations when instantiating objects.

6.2 What is Instruction reordering?

private SingletonObject(a){
	  / / the first step
     int x = 10;
	  / / the second step
     int y = 30;
     / / the third step
     Object o = new Object(); 
}
Copy the code

The above constructor, SingletonObject(), is reordered by the JVM, so the execution order may be out of order, but regardless of the execution order, the JVM eventually ensures that all instances are instantiated. If there are a lot of operations in the constructor, the JVM will return the object before all the properties in the constructor have been instantiated to improve efficiency. Double check the cause of null pointer problem is appeared in here, when a thread for lock instantiated, other threads can for instance use directly, because of the JVM instruction reordering, other threads to obtain objects may not be a complete object, so when using the instance would be a null pointer exception.

6.3 Optimization of double-checked lock mode

To solve the problem of null-pointer exceptions caused by double-checked locking, use the volatile keyword, which strictly follows the happens-before rule: writes must complete before reads.

public class DoubleCheckLockModelVolatile {
    /** * Add the volatile keyword to ensure that writes are complete before reads */
    private static volatile DoubleCheckLockModelVolatile instance;
    /** * privatize constructor */
    private DoubleCheckLockModelVolatile(a){}/** * provides a public access to instance *@return* /
    public static DoubleCheckLockModelVolatile getInstance(a){

        if (instance == null) {
            synchronized (DoubleCheckLockModelVolatile.class) {
                if (instance == null) {
                    instance = newDoubleCheckLockModelVolatile(); }}}returninstance; }}Copy the code

Static inner class schema

The static inner class mode is also known as the singleton holder mode. Instances are created by the inner class. Since the JVM does not load a static inner class during the loading of an external class, the inner class’s properties/methods are only loaded and initialized when called. Static attributes are modified static and are guaranteed to be instantiated only once, in a strict order.

public class StaticInnerClassMode {

    private StaticInnerClassMode(a){}/** * singleton holder */
    private static class InstanceHolder{
        private  final static StaticInnerClassMode instance = new StaticInnerClassMode();

    }

    /** * provides a public access to instance *@return* /
    public static StaticInnerClassMode getInstance(a){
        // Invoke the inner class attribute
        returnInstanceHolder.instance; }}Copy the code

This approach is similar to, but different from, the hungrier approach. Both use a classloading mechanism to ensure that only one thread initializes instances. Different places:

  1. The hungry way is as long asSingletonClasses are instantiated as soon as they are loaded, noLazy-LoadingThe role of;
  2. Static inner class mode inSingletonClasses are not instantiated immediately when they are loaded, but are called when they need to be instantiatedgetInstance()Method will be loadedSingletonInstanceClass to completeSingletonInstantiation of.

The static properties of a class are initialized only when the class is first loaded, so the JVM helps us keep our threads safe from entering while the class is initialized.

Therefore, in this way, no lock is added to ensure the safety of multithreading, and there is no performance impact and space waste.

Enumeration classes implement the singleton pattern

Because enumerations are thread-safe and only loaded once, the designers took full advantage of this feature to implement the singleton pattern. Enumerations are very simple to write, and enumerations are the only singleton pattern used that cannot be broken.

8.1 the sample

public class EnumerationMode {
    
    private EnumerationMode(a){}/** * Enumeration types are thread-safe and are loaded only once */
    private enum Singleton{
        INSTANCE;

        private final EnumerationMode instance;

        Singleton(){
            instance = new EnumerationMode();
        }

        private EnumerationMode getInstance(a){
            returninstance; }}public static EnumerationMode getInstance(a){

        returnSingleton.INSTANCE.getInstance(); }}Copy the code

8.2 Application:

  1. Objects that require frequent creation and destruction;
  2. Objects that take too much time or resources to create, but are often used.
  3. Tool class object;
  4. Objects that frequently access databases or files.

Problems and solutions of singleton mode

All methods except enumeration break singletons by reflection

9.1 Destruction of singleton Mode

/** * Take a static inner class implementation *@throws Exception
 */
@Test
public void singletonTest(a) throws Exception {
    Constructor constructor = StaticInnerClassMode.class.getDeclaredConstructor();
    constructor.setAccessible(true);

    StaticInnerClassMode obj1 = StaticInnerClassMode.getInstance();
    StaticInnerClassMode obj2 = StaticInnerClassMode.getInstance();
    StaticInnerClassMode obj3 = (StaticInnerClassMode) constructor.newInstance();

    System.out.println("The output is:"+obj1.hashCode()+"," +obj2.hashCode()+","+obj3.hashCode());
}
Copy the code

Console printing:

The output is: 1454171136145171 136119396 074Copy the code

From the output we can see that obj1 and obj2 are the same object, and obj3 is the new object. Obj3 is where we invoke a private constructor via reflection and create a new object.

9.2 How Can I Prevent Singleton Damage

If there are already instances, prevent new instances from being created.

public class StaticInnerClassModeProtection {

    private static boolean flag = false;

    private StaticInnerClassModeProtection(a){
        synchronized(StaticInnerClassModeProtection.class){
            if(flag == false){
                flag = true;
            }else {
                throw new RuntimeException("Instance already exists, get it by getInstance()!"); }}}/** * singleton holder */
    private static class InstanceHolder{
        private  final static StaticInnerClassModeProtection instance = new StaticInnerClassModeProtection();
    }

    /** * provides a public access to instance *@return* /
    public static StaticInnerClassModeProtection getInstance(a){
        // Invoke the inner class attribute
        returnInstanceHolder.instance; }}Copy the code

Testing:

/** * Checks in the constructor and throws RuntimeException * if present@throws Exception
 */
@Test
public void destroyTest(a) throws Exception {
    Constructor constructor = StaticInnerClassModeProtection.class.getDeclaredConstructor();
    constructor.setAccessible(true);

    StaticInnerClassModeProtection obj1 = StaticInnerClassModeProtection.getInstance();
    StaticInnerClassModeProtection obj2 = StaticInnerClassModeProtection.getInstance();
    StaticInnerClassModeProtection obj3 = (StaticInnerClassModeProtection) constructor.newInstance();

    System.out.println("The output is:"+obj1.hashCode()+"," +obj2.hashCode()+","+obj3.hashCode());
}
Copy the code

Console printing:

Under Caused by: Java. Lang. RuntimeException: instance already exists, please through the getInstance () method to get! at cn.van.singleton.demo.mode.StaticInnerClassModeProtection.<init>(StaticInnerClassModeProtection.java:22) ... 35 moreCopy the code

Ten,

10.1 Comparison of various implementations

The name of the The hungry mode Lazy mode Double-checked lock mode Static inner class implementation Enumeration class implementation
availability available Not recommended It is recommended to use It is recommended to use It is recommended to use
The characteristics of Lazy loading is not possible and may result in wasted space Unlocked threads are not safe; Poor locking performance Thread safety; Lazy loading; High efficiency Avoid thread insecurity, delay loading, high efficiency. Simple writing; Thread safety; Load once only

10.2 Example code address

Github sample code

10.3 Technical Exchange

  1. Travelogue blog: https://www.dustyblog.cn
  2. Dust blog – Gold digging
  3. Travelogue Blog – Blog Garden
  4. Github
  5. The public,


10.4 Reference Articles

Six design principles of object-oriented design