Summary: Starting with this article, I’ll take you through Kotlin’s design patterns. Tell me why you want to start this series. Mainly based on the following reasons:

  • Design patterns have always been a big obstacle for developers to understand Android source code. So to understand and to use the source code of some design idea and skill, first read the source code is the first step, and read the source code, and you need a design pattern and data structure algorithm (my article series algorithm and data structure also began a week) as the foundation, otherwise looks like fog, can only by rote others summary conclusion, ultimately unable to digest and understand.
  • 2. Kotlin’s implementation of design patterns is quite different from that of Java. It is more elegant and efficient to use Kotlin’s own features to implement design patterns than to use Java’s implementation of design patterns. Of course, I will compare each design pattern with the Kotlin implementation in order to understand it better.
  • 3. It is understood that Kotlin’s articles about the implementation of design patterns are still relatively few in China, so I want to systematically write a series of articles about Kotlin encounter design patterns.

Say the ultimate goal, the ultimate goal is to have the basic ability in the analysis of the source code can stand in a global perspective to think, rather than a head into the vast source cannot extricate themselves lost. Some source code analysis articles will follow. So stay on top of these basic tools for a while.

Introduce a,

The singleton pattern is the most common design pattern among developers, and the simplest of the 23 design patterns. Most developers know how it works. The singleton pattern, as its name implies, applies when the singleton object class must have only one instance of the object. In some application scenarios we only need a globally unique object instance to schedule global behavior. In some cases, to avoid creating multiple instances repeatedly, singleton mode is often used to ensure that there is only one instance object globally for the sake of system resource overhead.

Second, the definition of

A class is guaranteed to have only one instance object, which is instantiated internally and provides a global access point to get it.

Three, basic requirements

  • 1, constructor private, private modification, mainly to prevent external private creation of the object instance of the singleton class
  • 2. Provide a global access point to the instance object. In Java, a singleton class object is usually returned by a public static method or enumeration
  • 3, in the multi-threaded environment to ensure that the singleton class has only one object instance, and in the multi-threaded environment to obtain the singleton object instance to ensure thread safety.
  • 4. Ensure that the singleton class has only one object instance during deserialization.

Four, use scenarios

It is generally used to determine that a class requires only one instance object to avoid the resource and performance overhead of frequently creating multiple object instances. For example, common database connections or IO operations.

UML class diagram

6. Hangry singleton

Hanchian singleton pattern is a simple way to implement singleton pattern. It has a feature that the singleton object will be instantiated whether it is needed or not.

1, Kotlin implementation

Implementing a hanchian singleton in Kotlin is very, very easy. You just need to define an object expression. You don’t need to manually set the constructor to private and provide global access points.

object KSingleton : Serializable {// Implement the Serializable interface, which controls deserialization through the private, instantiated readResolve method
    fun doSomething(a) {
        println("do some thing")}private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
        return KSingleton// Since the readResolve hook method is called when deserializing, you only need to return the current KSingleton object instead of creating a new one}}// Use KSingleton in Kotlin
fun main(args: Array<String>) {
    KSingleton.doSomething()Call a method in a singleton class as if it were a static method
}
Use KSingleton in Java
public class TestMain {
    public static void main(String[] args) {
        KSingleton.INSTANCE.doSomething();// Call the method in the singleton class by getting INSTANCE, a static INSTANCE of the public singleton class}}Copy the code

KSingleton decompiles into Java code

public final class KSingleton implements Serializable {
   public static final KSingleton INSTANCE;

   public final void doSomething(a) {
      String var1 = "do some thing";
      System.out.println(var1);
   }

   private final Object readResolve(a) {
      return INSTANCE;// You can see that the readResolve method returns INSTANCE directly instead of creating a new INSTANCE
   }

   static {// The static code block initializes the KSingleton instance, whether it is used or not, as soon as the KSingleton is loaded,
   // The static code block will be called and the KSingleton INSTANCE will be created and assigned to INSTANCE
      KSingleton var0 = newKSingleton(); INSTANCE = var0; }}Copy the code

One might wonder: without seeing the constructor privatized, this is already limited at the compiler level, and you can’t create new singletons in Either Java or Kotlin.

2. Java implementation

public class Singleton implements Serializable {
    private Singleton(a) {// Privatize the constructor
    }

    private static final Singleton mInstance = new Singleton();

    public static Singleton getInstance(a) {// Provide a public function to get a singleton
        return mInstance;
    }

    // Prevent the singleton from regenerating the object during deserialization
    private Object readResolve(a) throws ObjectStreamException {
        returnmInstance; }}Copy the code

Compare Kotlin with Java’s hungry singleton implementation and see if Kotlin would be much simpler than Java.

Thread-safe lazy singleton

However, sometimes we don’t want to create the singleton instance when the class loads, but rather we want to initialize it when we use it. Hence the lazy singleton pattern

1, Kotlin implementation

class KLazilySingleton private constructor() : Serializable {
    fun doSomething(a) {
        println("do some thing")}companion object {
        private var mInstance: KLazilySingleton? = null
            get() {
                returnfield ? : KLazilySingleton() }@JvmStatic
        @Synchronized// Add a synchronized lock
        fun getInstance(a): KLazilySingleton {
            return requireNotNull(mInstance)
        }
    }
    // Prevent the singleton from regenerating the object during deserialization
    private fun readResolve(a): Any {
        return KLazilySingleton.getInstance()
    }
}
// called in Kotlin
fun main(args: Array<String>) {
    KLazilySingleton.getInstance().doSomething()
}
// call in Java
 KLazilySingleton.getInstance().doSomething();
Copy the code

2. Java implementation

class LazilySingleton implements Serializable {
    private static LazilySingleton mInstance;

    private LazilySingleton(a) {}// Privatize the constructor

    public static synchronized LazilySingleton getInstance(a) {//synchronized synchronized locks ensure thread safety when multiple threads call getInstance
        if (mInstance == null){
            mInstance = new LazilySingleton();
        }
        return mInstance;
    }
    
    private Object readResolve(a) throws ObjectStreamException {// Prevent deserialization
        returnmInstance; }}Copy the code

DCL(Double check Lock) transform lazy singleton

We know that thread-safe singletons use synchronized to lock the getInstance method and acquire the lock every time the method is called, but if the singleton is already initialized, there is no need to acquire the lock and simply return the singleton instance. Hence the DCL implementation singleton approach.

1. DCL implementation in Java

//DCL implements the singleton pattern
public class LazySingleTon implements Serializable {
    // Privatize static members. Use the volatile keyword because the DCL will fail
    private volatile static LazySingleTon mInstance = null; 

    private LazySingleTon(a) { // Privatize the constructor
    }

    // The function that gets the singleton object publicly
    // Double Check Lock (DCL) can not only initialize singleton when needed, but also ensure thread safety. After the singleton is initialized, the call to getInstance does not require synchronization Lock
    public static LazySingleTon getInstance(a) {
        if (mInstance == null) {// To prevent the synchronization lock from repeating after the singleton is initialized
            synchronized (LazySingleTon.class) {
                if (mInstance == null) {
                    mInstance = newLazySingleTon(); }}}return mInstance;
    }

    private Object readResolve(a) throws ObjectStreamException {
        returnmInstance; }}Copy the code

2. DCL implementation in Kotlin

There is a natural feature in Kotlin that supports thread-safe DCL singletons that are very, very simple in just three lines of code: the Companion Object + lazy proxy.

class KLazilyDCLSingleton private constructor() : Serializable {//private constructor(

    fun doSomething(a) {
        println("do some thing")}private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
        return instance
    }
    
    companion object {
        // Using the @jvmStatic annotation, instance is called in Java as if it were a static function,
        / / similar KLazilyDCLSingleton. GetInstance (), if do not add annotations, in Java must call like this: KLazilyDCLSingleton.Com panion. GetInstance ().
        @JvmStatic
        // Use the lazy proxy and set LazyThreadSafetyMode to SYNCHRONIZED to ensure thread safety
        val instance: KLazilyDCLSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KLazilyDCLSingleton() }
    }
}

// in Kotlin, instance is called directly by the KLazilyDCLSingleton class name
fun main(args: Array<String>) {
    KLazilyDCLSingleton.instance.doSomething()
}
// call in Java
public class TestMain {
    public static void main(String[] args) {
    / / added @ JvmStatic annotations, can be directly KLazilyDCLSingleton. GetInstance (), won't break the Java call the habit, and Java calls the same way.
       KLazilyDCLSingleton.getInstance().doSomething();
       // Without the @jvmstatic annotation, it can only be called with CompanionKLazilyDCLSingleton.Companion.getInstance().doSomething(); }}Copy the code

Note: it is recommended to add the @jVMstatic annotation to the example above. The Kotlin language is very careful not to let Java developers break their call habits and make calls that are not even aware that Kotlin wrote them, because external calls are made in the same way as Java. Java developers might be stunned if they were to expose their Companion objects.

The power of lazy and The Companion Object may be confusing to many people, but let’s take a look at decompiled Java code to make it clear:

public final class KLazilyDCLSingleton implements Serializable {
   @NotNull
   private static final Lazy instance$delegate;
   / / Companion to provide public global point of access, KLazilyDCLSingleton.Com panion actually a singleton pattern of the type of the hungry
   public static final KLazilyDCLSingleton.Companion Companion = new KLazilyDCLSingleton.Companion((DefaultConstructorMarker)null);
   public final void doSomething(a) {
      String var1 = "do some thing";
      System.out.println(var1);
   }

   private final Object readResolve(a) {
      return Companion.getInstance();
   }

   private KLazilyDCLSingleton(a) {}static {KLazilyDCLSingleton class is loaded. The static code block initializes the KLazilyDCLSingleton class with its Lazy proxy object.
   // KLazilyDCLSingleton instance is not initialized according to the lazy load rule
      instance$delegate = LazyKt.lazy(LazyThreadSafetyMode.SYNCHRONIZED, (Function0)null.INSTANCE);
   }

   // $FF: synthetic method
   public KLazilyDCLSingleton(DefaultConstructorMarker $constructor_marker) {
      this(a); }@NotNull
   public static final KLazilyDCLSingleton getInstance(a) {
      return Companion.getInstance();// Add @jvmstatic to getInstance to add Companion. So Java caller KLazilyDCLSingleton directly. The getInstance () to obtain the singleton instance
   }

   // The Companion static inner class is actually a singleton
   public static final class Companion {
      // $FF: synthetic field
      static finalKProperty[] ? delegatedProperties =new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(KLazilyDCLSingleton.Companion.class), "instance"."getInstance()Lcom/mikyou/design_pattern/singleton/kts/KLazilyDCLSingleton;"))};

      / * *@deprecated* /
      // $FF: synthetic method
      @JvmStatic
      public static void instance$annotations() {
      }

      @NotNull
      // Note that this method is where the final instance initialization and fetching will take place
      public final KLazilyDCLSingleton getInstance(a) {
         // Get the proxy objectLazy var1 = KLazilyDCLSingleton.instance$delegate; KProperty var3 = ? delegatedProperties[0];
         // The proxy object's getValue method is the entry to initializing and getting instance. Internally it checks if instance has been initialized and returns the newly created object,
         // Returns the last initialized object. So instance is initialized only if the instance is actually needed to call the getInstance method.
         return (KLazilyDCLSingleton)var1.getValue();
      }

      private Companion(a) {// Privatize the Companion constructor
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
Copy the code

Kotlin’s lazy proxy implements source code analysis internally

// Expect keyword tags This function is platform-dependent, and we need to find the corresponding actual keyword implementation that represents a platform-dependent implementation
public expect fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>

// Implement lazy for one platform in multiple platforms
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {SynchronizedLazyImpl = SynchronizedLazyImpl
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE// To address the inconsistency between main memory and working memory caused by instruction reordering by the DCL, use Volatile primitive annotations. Why does Volatile solve this problem
    private vallock = lock ? :this

    override val value: T
        get() {// Get accessors are called when value is called externally
            val _v1 = _value
            if(_v1 ! == UNINITIALIZED_VALUE) {If the value is already initialized, return _v1 to avoid unnecessary resource overhead caused by synchronized.
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if(_v2 ! == UNINITIALIZED_VALUE) {// Perform the second level Check, mainly for _v2 to be initialized directly back
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                // If no initializer is used, run initializer!! () lambda,
                / / effectively execute external call incoming by lazy (LazyThreadSafetyMode. SYNCHRONIZED) {KLazilyDCLSingleton ()} KLazilyDCLSingleton(), which returns the KLazilyDCLSingleton instance object
                    valtypedValue initializer!! () _value = typedValue// Store the instance object in _value
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(a): Boolean= _value ! == UNINITIALIZED_VALUEoverride fun toString(a): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(a): Any = InitializedLazyImpl(value)
}    
Copy the code

4. Analysis and solution of multithreading safety problems in DCL

  • Problem analysis:

The DCL has multithreaded safety issues, which we all know are mainly caused by inconsistencies between native and working memory data and reordering (instruction reordering or compiler reordering). So what’s the problem with DCL? First, mInstance = new LazySingleton() is not an atomic operation but is divided into three steps:

  • Allocate memory to LazySingleton instance
  • 2. Call LazySingleton’s constructor to initialize the member fields
  • 3. Reference mInstance to the allocated memory space (mInstance is not null)

In the Java memory model prior to JDK1.5, the order of Cache and register write back to main memory cannot be guaranteed. It may be 1-2-3 or 1-3-2. If step 1 and step 3 are executed in line A, then switch to thread B. Since the third step has been executed in thread A, mInstance is not null. Therefore, thread B directly executes mInstance, which causes an error when the second step is not executed.

  • Solve a problem:

To address this issue, the volatile keyword has been externalized since JDK1.5 to ensure that the latest valid values are fetched from main memory each time. Private volatile static LazySingleTon mInstance = null;

Static inner class singleton

Although DCL can solve resource consumption, redundant synchronized synchronization, thread safety and other problems to a certain extent, DCL failure can also exist in some cases. Although the DCL failure can be solved by using volatile primitive after JDK1.5, it is still not an elegant solution. The singleton pattern of DCL is generally not recommended in multithreaded environments. So the static inner class singleton implementation is introduced

1, Kotlin implementation

class KOptimizeSingleton private constructor(): Serializable {//private constructor(
    companion object {
        @JvmStatic
        fun getInstance(a): KOptimizeSingleton {// Global access point
            return SingletonHolder.mInstance
        }
    }

    fun doSomething(a) {
        println("do some thing")}private object SingletonHolder {// Static inner class
        val mInstance: KOptimizeSingleton = KOptimizeSingleton()
    }
    
    private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
        return SingletonHolder.mInstance
    }
}
Copy the code

2. Java implementation

// Use static internal singleton mode
public class OptimizeSingleton implements Serializable {
    // Privatize the constructor
    private OptimizeSingleton(a) {}// Static private inner class
    private static class SingletonHolder {
        private static final OptimizeSingleton sInstance = new OptimizeSingleton();
    }

    // The function that gets the singleton object publicly
    public static OptimizeSingleton getInstance(a) {
        return SingletonHolder.sInstance;
    }
    
    public void doSomeThings(a) {
        System.out.println("do some things");
    }
    
    // Prevent deserialization from recreating the object
    private Object readResolve(a) {
        returnSingletonHolder.sInstance; }}Copy the code

Enumerate singletons

In the example above, I will implement the Serializable interface and the readResolve method. This is done so that deserialization recreates the object so that the original singleton is no longer unique. A new instance object can be obtained by serializing a singleton by writing it to disk and then reading it from disk. Even if the constructor is private, deserialization creates new instances of the singleton class through other special means. However, to give developers control over deserialization, we provide a special hook method called readResolve, which simply returns the original instance in readResolve and does not create a new object.

Enumeration singletons are implemented to prevent deserialization, because we all know that enumeration class deserialization does not create new object instances. Java serialization mechanism made a special handling the enumerated types, generally in the sequence enumerated types, will only store enumeration class references, and enumerated constants names, the deserialization process, this information is used to look for in the runtime environment of enumeration object, serialization mechanism to ensure that an enum type will only find existing enumerated type instance, Instead of creating a new instance.

1, Kotlin implementation

enum class KEnumSingleton {
    INSTANCE;

    fun doSomeThing(a) {
        println("do some thing")}}// called in Kotlin
fun main(args: Array<String>) {
    KEnumSingleton.INSTANCE.doSomeThing()
}
// call in Java
 KEnumSingleton.INSTANCE.doSomeThing();
Copy the code

2. Java implementation

public enum EnumSingleton {
    INSTANCE;
    public void doSomeThing(a) {
        System.out.println("do some thing"); }}// Call mode
EnumSingleton.INSTANCE.doSomeThing();
Copy the code

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~

Welcome to Kotlin’s series of articles:

Data Structure and Algorithm series:

  • Binary Search for algorithms every Week (described by Kotlin)

Kotlin original series:

  • How do you fully parse the type system in Kotlin
  • How do you make your callbacks more Kotlin
  • Jetbrains developer briefing (3) Kotlin1.3 new features (inline class)
  • JetBrains developer briefing (2) new features of Kotlin1.3 (Contract and coroutine)
  • Kotlin/Native: JetBrains Developer’s Day (Part 1
  • How to overcome the difficulties of generic typing in Kotlin
  • How to overcome the difficulties of Generic typing in Kotlin (Part 2)
  • How to overcome the difficulties of Generic typing in Kotlin (Part 1)
  • Kotlin’s trick of Reified Type Parameter (Part 2)
  • Everything you need to know about the Kotlin property broker
  • Source code parsing for Kotlin Sequences
  • Complete analysis of Sets and functional apis in Kotlin – Part 1
  • Complete parsing of lambdas compiled into bytecode in Kotlin syntax
  • On the complete resolution of Lambda expressions in Kotlin’s Grammar
  • On extension functions in Kotlin’s Grammar
  • A brief introduction to Kotlin’s Grammar article on top-level functions, infix calls, and destruct declarations
  • How to Make functions call Better
  • On variables and Constants in Kotlin’s Grammar
  • Elementary Grammar in Kotlin’s Grammar Essay

Translated by Effective Kotlin

  • The Effective Kotlin series considers arrays of primitive types for optimal performance (5)
  • The Effective Kotlin series uses Sequence to optimize set operations (4)
  • Exploring inline modifiers in higher-order functions (3) Effective Kotlin series
  • Consider using a builder when encountering multiple constructor parameters in the Effective Kotlin series.
  • The Effective Kotlin series considers using static factory methods instead of constructors (1)

Translation series:

  • Remember a Kotlin official document translation PR(inline type)
  • Exploration of autoboxing and High performance for inline classes in Kotlin (ii)
  • Kotlin inline class full resolution
  • Kotlin’s trick of Reified Type Parameter
  • When should type parameter constraints be used in Kotlin generics?
  • An easy way to remember Kotlin’s parameters and arguments
  • Should Kotlin define functions or attributes?
  • How to remove all of them from your Kotlin code! (Non-empty assertion)
  • Master Kotlin’s standard library functions: run, with, let, also, and apply
  • All you need to know about Kotlin type aliases
  • Should Sequences or Lists be used in Kotlin?
  • Kotlin’s turtle (List) rabbit (Sequence) race

Actual combat series:

  • Use Kotlin to compress images with ImageSlimming.
  • Use Kotlin to create a picture compression plugin.
  • Use Kotlin to compress images.
  • Simple application of custom View picture fillet in Kotlin practice article