Android Kotlin 设计模式之单例模式

Android Kotlin 单例模式

前言

最近学习Kotlin,所以也在对比Kotlin和java的差异,在java里有23种设计模式,但在Kotlin里面的写法是不相同的,所以在此记下笔记和心得。

单例模式
单例模式的核心是确保某一个类有且只有一个实例,并且自行实例化,向整个系统提供这个唯一实例

应用场景

单例模式在Android里运用也是比较广泛的
这个是为了减少内存消耗(只有一个实例)和方便管理数据(多个实例内存不共享)存在的
比如我们很常用的Glide:
我们Glide.with就是调用一个单例来着

//Glide.java
public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }

//RequestManagerRetriever.java
 private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();

 public static RequestManagerRetriever get() {
        return INSTANCE;
    }

以及我们对各种工具类的封装都会用到

常见的单例模式写法

常见的单例模式有以下多种写法,下面会一一对比优劣势及Kotln与java写法上的差异
不过核心都是:

  • 构造方法私有化,不能通过new 关键字来创建
  • 在该类内部创建唯一的实例化对象
  • 一个共有方法起获取到这一个唯一的实例化对象

饿汉式

饿汉式就是声明静态对象时初始化,一开始就存在,比较费内存

//java
public class SingletonByJava {

    private static SingletonByJava instance = new SingletonByJava();

    private SingletonByJava(){

    }

    public static SingletonByJava getInstance(){
        return  instance;
    }
}
//kotlin
object SingletonByKotlin {

}

没错,饿汉式的声明在Kotlin非常简单,只需使用object这个关键字,这个关键字的意思是声明一个对象
因为在Kotlin里面是没有静态方法的,而用object关键字声明会被编译成:
一个类拥有一个静态成员来持有对自己的引用,并且这个静态成员的名称为INSTANCE,当然这个INSTANCE是单例的
这里我们来通过Kotlin对应的字节码转成java代码就能很容易看出

public final class SingletonByKotlin {
   public static final SingletonByKotlin INSTANCE;

   static {
      SingletonByKotlin var0 = new SingletonByKotlin();
      INSTANCE = var0;
   }
}

使用上也和正常的单例模式一样

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e(TAG,"java"+SingletonByJava.getInstance().toString())
        Log.e(TAG,"Kotlin"+SingletonByKotlin.toString())

        Log.e(TAG,"java"+SingletonByJava.getInstance().toString())
        Log.e(TAG,"Kotlin"+SingletonByKotlin.toString())
    }
}

懒汉式

懒汉式对于饿汉式的差别的就是创建是不实例化对象,等到使用时再实例化,可以节省一些内存,但是线程不安全,可能会出现创建多个实例。

//java
public class SingletonByJava {
    private static SingletonByJava instance;
    private SingletonByJava(){

    }
    private static SingletonByJava getInstance(){
        if (instance == null){
            instance = new SingletonByJava();
        }
        return instance;
    }
}
//Kotlin
//主构造函数私有化
class SingletonByKotlin private constructor(){
    companion object {
        private var instance: SingletonByKotlin? = null
        //自定义属性访问器
        get() {
            if (field == null)
                field = SingletonByKotlin()
            return field
        }
        //这里不能使用getInstance作为方法名,因为companion object内部已经有了getInstance这个方法了
        fun get(): SingletonByKotlin{
            return instance!!
        }
    }
}

静态内部类式

写法基本同java

//java
public class SingletonByJava {
    private static class SingletonHolder{
        private static SingletonByJava instance = new SingletonByJava();
    }
    public static SingletonByJava getInstance(){
        return SingletonHolder.instance;
    }
}
//Kotlin
class SingletonByKotlin {
    companion object {
        val instance = SingletonHolder.holder
    }
    private object SingletonHolder{
        val holder = SingletonByKotlin()
    }
}

线程安全懒汉式

众所周知,懒汉式是线程不安全的,要想让其线程安全,那么就需要使用同步锁
在Kotlin中,如果你需要将方法声明为同步,使用@Synchronized注解

//java
public class SingletonByJava {
    private static SingletonByJava instance;
    private SingletonByJava(){

    }
    //使用同步锁
    public static synchronized SingletonByJava getInstance(){
        if (instance == null){
            instance = new SingletonByJava();
        }
        return instance;
    }
}
//Kotlin
class SingletonByKotlin private constructor(){
    companion object {
        private var instance: SingletonByKotlin? = null
        get() {
            if (field == null)
                field = SingletonByKotlin()
            return field
        }
        @Synchronized
        fun get(): SingletonByKotlin{
            return instance!!
        }
    }
}

双重校验锁式(Double Check Lock)

这个网上建议和使用最多的方法,因为前面的线程安全懒汉式其实也不能算是完全安全的

//java
public class SingletonByJava {
    private volatile static SingletonByJava instance;
    private SingletonByJava(){

    }
    public static SingletonByJava getInstance(){
        if (instance == null){
            synchronized (SingletonByJava.class){
                if (instance == null){
                    instance = new SingletonByJava();
                }
            }
        }
        return instance;
    }
}
//Kotlin
class SingletonByKotlin private constructor(){
    companion object {
        @Volatile
        private var instance: SingletonByKotlin? = null
        get() {
            if (field == null){
                synchronized(SingletonByKotlin::class){
                    if (field == null)
                        field = SingletonByKotlin()
                }
            }
            return field
        }
        @Synchronized
        fun get(): SingletonByKotlin{
            return instance!!
        }
    }
}
//极短版Kotlin写法
class SingletonByKotlin private constructor(){
    companion object {
        val instance: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
            SingletonByKotlin()
        }
    }
}

极短版的Kotlin写法用到的知识点比较多,慢慢解释吧
首先是用了高阶函数和自身语法的一些优化,高阶函数简单的说就是将函数用作参数或返回值的函数,然后又使用了Kotlin的委托。在Kotlin的标准库里,内置了很多工厂方法来实现属性的委托。

延迟属性 lazy

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托。 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

//LazyThreadSafetyMode.KT
public enum class LazyThreadSafetyMode {
    /**
     * Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
     *加锁来保证只有一个线程可以初始化实例
     */
    SYNCHRONIZED,
    /**
     * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
     * but only the first returned value will be used as the value of [Lazy] instance.
     */
    PUBLICATION,
    /**
     * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
     *
     * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
     */
    NONE,
}

可以看到我们传入的是mode = LazyThreadSafetyMode.SYNCHRONIZED,而LazyThreadSafetyMode.SYNCHRONIZED从注释中可以看出是加锁保证单例线程安全
然后走的LazyThreadSafetyMode.SYNCHRONIZED是SynchronizedLazyImpl,而SynchronizedLazyImpl实现了Lazy接口

Lazy接口

public interface Lazy<out T> {
    /**
     * Gets the lazily initialized value of the current Lazy instance.
     * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
     * 获取当前实例化对象
     * 一旦被实例化后,那么该对象就不会再被改变
     */
    public val value: T
    /**
     * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
     * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
     * 返回true表示,已经延迟实例化过了,false 表示,没有被实例化,
     * 一旦方法返回true,该方法会一直返回true,且不会再继续实例化
     */
    public fun isInitialized(): Boolean
}

SynchronizedLazyImpl的实现

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
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this
    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
            //判断是否已经初始化过,如果初始化过直接返回,不在调用高级函数内部逻辑
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()//调用高级函数获取其返回值
                    _value = typedValue //将返回值赋值给_value,用于下次判断时,直接返回高级函数的返回值
                    initializer = null
                    typedValue
                }
            }
        }
    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

我们可以看出,SynchronizedLazyImpl 重写了Lazy接口的value属性,并重新定义了其属性访问器,这个逻辑和java的双重检验是一致的。

所以我们先再回首看下DCL的实现代码

val instance: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
            SingletonByKotlin()
        }

lazy(mode: LazyThreadSafetyMode, initializer: () -> T)
这个是高阶函数,传入一个常量和一个函数,按道理来说写法应该是这样的:

 val instancne: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED,{SingletonByKotlin()})

但由于Kotlin自身语法的原因,你这样写是不可以的,你要把函数写在后面。
然后可以看到我们使用了lazy实际上只是实例化了SynchronizedLazyImpl对象而已
并没有进行值的获取,那么它是怎么拿到高阶函数的返回值的?
这个就是Kotlin的委托了,通过by关键字,在 by 后面的表达式是该委托, 因为属性对应的 get()和 set()会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数及setValue()(是val的话就没有写了)
这里Lazy已经帮我们写好了getValue()了

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

所以整体逻辑就是,我把SingletonByKotlin()传过去告诉怎么实例,然后在lazy进行了双重检查(逻辑类似),然因为使用的是委托,后续调用都是返回结果了(妥妥的DLC没毛病)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 单例设计模式有着非常广泛的应用,而平时我们接触的都是些Java的实现方式,关于Kotlin的单例模式则很少被提及,...
    呱呱_阅读 1,488评论 3 5
  • 简述: 从这篇文章开始,我将带领大家一起来探讨一下Kotlin眼中的设计模式。说下为什么想着要开始这么一个系列文章...
    熊喵先森阅读 969评论 1 2
  • 前言 最近在学习Kotlin这门语言,在项目开发中,运用到了单例模式。因为其表达方式与Java是不同的。所以对不同...
    AndyJennifer阅读 104,288评论 19 193
  • 想念玉米水稻平原的小麦 一些树的果实和地下的块茎 绿油油鲜嫩多汁的青菜 奔跑的牛羊和水里游泳的鱼 想念遥远的或许已...
    长山独白阅读 134评论 0 1
  • 今天看完了《一个人的朝圣2》,这是第2遍。 在《一个人的朝圣》里,65岁的哈罗德,87天行走627英里,只为了一个...
    没有昵称呀丫丫阅读 679评论 0 1