美文网首页
Android Kotlin 设计模式之单例模式

Android Kotlin 设计模式之单例模式

作者: 水天滑稽天照八野滑稽石 | 来源:发表于2019-04-25 17:03 被阅读0次

    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没毛病)

    相关文章

      网友评论

          本文标题:Android Kotlin 设计模式之单例模式

          本文链接:https://www.haomeiwen.com/subject/izotwqtx.html