Understand software design patterns (1): Singleton pattern and strategy pattern

Preface

        Software design patterns and design principles are very important and are used in almost all development frameworks and components. For example, the singleton pattern in this section is used in SpringBean. In this article, Lizhi will carefully sort out the relevant knowledge points about the singleton mode and the strategy mode. The more important thing is to master the conventional writing method of the singleton mode. Hope it helps those in need~~~


Article directory

Preface

1. Singleton mode singleton

1.1 Hungry Chinese Style

1.2 Lazy Man Style

1.3 Lazy style + pessimistic lock

1.4 Double check lock

1.5 Static inner class writing method

1.6 Enumeration singleton 

2. Strategy Model Strategy

Summarize


1. Singleton mode singleton

        The singleton pattern ensures that only one instance is created and avoids the creation of multiple instances in the same project. In fact, in a class loading, only one instance of the current class object will be created. We cannot instantiate the object through the new method, but can only call the getInstance method provided by the class object to obtain the instantiated object.

1.1 Hungry Chinese Style

Hungry style is a relatively commonly used singleton pattern. A private variable is statically defined in the class and the class object is instantiated when the class is loaded. By returning the INSTANCE instance object in the getInstance method.

package com.mashibing.dp.singleton;

public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();

    private Mgr01() {};

    public static Mgr01 getInstance() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}

Hungry Chinese style is loaded immediately, has almost no shortcomings except preventing deserialization problems, and it is thread-safe and simple to operate.

1.2 Lazy Man Style

The lazy style does not instantiate the object when loading the class. It is lazy loading (loading on demand), but there will be thread safety issues. 

For example, if two threads hit INSTANCE at the same time, there may be a risk of new instance objects being created at the same time, so the thread is not safe. In the example demo below, you can see a method described by a lambda expression. Lambda expression is a shorthand for the anonymous inner class of the thread Runnable interface. This is because we only write one internal method in Runnable here.

package com.mashibing.dp.singleton;

public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {
    }
    /**
     * 懒汉式
     */
    public static Mgr03 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->
                System.out.println(Mgr03.getInstance().hashCode())
            ).start();
        }
    }
}

 The original writing method of new Thread() here is

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Mgr03.getInstance().hashCode())
        }
    }).start();

1.3 Lazy style + pessimistic lock

Lazy locking is actually relatively simple. Just use the synchronized keyword modification and add pessimistic locking. The operation is relatively simple and it solves the thread safety problem relatively completely. However, this is based on the sacrifice of efficiency, and it is not Serialization safe and reflection safe.

package com.mashibing.dp.singleton;

public class Mgr04 {
    private static Mgr04 INSTANCE;

    private Mgr04() {
    }

    /**
     * 懒汉式+同步锁
     * @return
     */
    public static synchronized Mgr04 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr04.getInstance().hashCode());
            }).start();
        }
    }
}

1.4 Double check lock

        Previously, we added a lock mechanism to solve the thread safety problem caused by the lazy style, but it caused a decrease in code efficiency. The double checking mechanism can be used here to solve the code efficiency problem, optimize the code performance, and also ensure thread safety and lazy loading mechanism. But it is indeed a bit complicated to implement and difficult to debug.

package com.mashibing.dp.singleton;

public class Mgr06 {
    //这里需要加上volatile的原因是因为Java中在编译中指令重排比较频繁,如果不加volatile会出现问题,
    private static volatile Mgr06 INSTANCE; //JIT

    private Mgr06() {
    }
    /**
     *双重检查单例写法
     * @return
     */
    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}

What needs to be noted here is that INSTANCE needs to be modified with the volatile keyword in the static variable!

The role of volatile key

  • Ensure the visibility of INSTANCE variables to prevent null pointer exceptions

        When a variable modified by volatile is accessed by a thread, it will be forced to read the value of the variable from the main memory rather than from the local cache to ensure the visibility and orderliness of the shared variable. After the variable is modified, the thread will Forces the updated value to be flushed back to main memory instead of just updating the thread's local cache. The problem of null pointer exception may be because other threads do not immediately obtain the modified variable value that is not modified by the volatile keyword.

  • Prevent command reordering

        Instruction rearrangement is an operation performed by the CPU to improve program execution efficiency. If the INSTANCE variable is not modified volatile, thread safety may not be guaranteed.

1.5 Static inner class writing method

         You can see that the way to write a static inner class is to customize a private static inner class in the object class, instantiate the object in it and assign it to a static constant. It not only realizes lazy loading of instantiated objects, but also ensures thread safety. The disadvantage of this class is that the restrictions on passing parameters may not be used in some scenarios.

package com.mashibing.dp.singleton;

public class Mgr07 {

    private Mgr07() {
    }

    private static class Mgr07Holder {
        private final static Mgr07 INSTANCE = new Mgr07();
    }

    /**
     * 静态内部类的写法
     * @return
     */
    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }


}

1.6 Enumeration singleton 

Enumeration singleton is the most perfect singleton mode, which effectively solves the deserialization problem of Java classes and achieves sequence safety and reflection safety. But enumeration singletons are not lazy loaded, nor can they be inherited.

package com.mashibing.dp.singleton;

/**
 * 不仅可以解决线程同步,还可以防止反序列化。
 */
public enum Mgr08 {

    INSTANCE;

    public void m() {}

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }

}

The singleton mode of non-enumerated classes will have deserialization problems. At this time, we can use Java's reflection mechanism to load class objects through the .class file in Java.

The reason why the enumeration singleton cannot be deserialized: the enumeration class has no constructor.

Regarding the double-check lock, please refer to the article by the Nuggets boss. The source is as follows:

https://juejin.cn/post/7206529406612062268?searchId=20230905212839127143297911190A3F76#heading-16


2. Strategy Model Strategy

        The strategy model is relatively simple and is often used in daily development. The strategy model generally encapsulates the different execution methods of a method. The strategy model separates objects and behaviors and is a behavioral model. Behaviors are divided into behavior strategy interfaces and classes that implement behaviors.

main file 

The main program calls the comparison class Sort, passing in the class object and the implementation of the corresponding comparator interface. 

package com.mashibing.dp.strategy;

import java.util.Arrays;

/**
 * writing tests first!
 * extreme programming
 */
public class Main {
    public static void main(String[] args) {
        Cat[] a = {new Cat(3, 3), new Cat(5, 5), new Cat(1, 1)};
        Sorter<Cat> sorter = new Sorter<>();
//        Dog[] b = {new Dog(3), new Dog(5), new Dog(1)};
//        Sorter<Dog> sorter = new Sorter<>();
       
        /**
         * 策略模式的选择,通过类加载的方式实现功能,代码的拓展性更强
         */
        sorter.sort(a,new CatWeightComparator());
        System.out.println(Arrays.toString(a));
        sorter.sort(a,new CatHeightComparator());
        System.out.println(Arrays.toString(a));
    }
}

Sort class

Customize a strategy selection class, and call the compare method in the comparator interface that has been overridden to implement the strategy model of the incoming comparison class. 

package com.mashibing.dp.strategy;

public class Sorter<T> {

    public void sort(T[] arr, Comparator<T> comparator) {
        for(int i=0; i<arr.length - 1; i++) {
            int minPos = i;

            for(int j=i+1; j<arr.length; j++) {
                minPos = comparator.compare(arr[j],arr[minPos])==-1 ? j : minPos;
            }
            swap(arr, i, minPos);
        }
    }

}

Strategy implementation class

The strategy interface needs to implement the Comparator interface in Java.util and rewrite the compare method in it to implement the strategy logic encapsulation of the object class.

package com.mashibing.dp.strategy;

public class CatHeightComparator implements Comparator<Cat> {
    @Override
    public int compare(Cat o1, Cat o2) {
        if(o1.height > o2.height) return -1;
        else if (o1.height < o2.height) return 1;
        else return 0;
    }
}

        In fact, the simplest application of the strategy model is to judge the execution strategy through if...else..., but this method is relatively confusing and has not very good scalability. Therefore, it is necessary to implement classes and Java generics through interfaces. Type to customize some strategies to choose from.


Summarize

        In the above content, Lizhi mainly sorted out the singleton pattern and strategy pattern, which are two of the twenty-three software design patterns. Understand the writing methods of several typical singleton patterns. Lizhi will continue to learn in the following articles. And organize the output, I hope it will get better and better in the future hahahahaha~~~

Today has become the past, but we still look forward to the future tomorrow! I am Xiaolizhi, and I will accompany you on the road of technological growth. Coding is not easy, so please raise your little paw and give me a thumbs up, hahaha~~~ Bixinxin♥~~~

Guess you like

Origin blog.csdn.net/qq_62706049/article/details/132694837