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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

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