在Kotlin中带参数的单例

Lor*_*nMK 43 android kotlin

我正在尝试将Android应用程序从Java转换为Kotlin.应用程序中有一些单身人士.我在没有构造函数参数的情况下为单例使用了伴随对象.还有另一个单例采用构造函数参数.

Java代码:

public class TasksLocalDataSource implements TasksDataSource {

    private static TasksLocalDataSource INSTANCE;

    private TasksDbHelper mDbHelper;

    // Prevent direct instantiation.
    private TasksLocalDataSource(@NonNull Context context) {
        checkNotNull(context);
        mDbHelper = new TasksDbHelper(context);
    }

    public static TasksLocalDataSource getInstance(@NonNull Context context) {
        if (INSTANCE == null) {
            INSTANCE = new TasksLocalDataSource(context);
        }
        return INSTANCE;
    }
}
Run Code Online (Sandbox Code Playgroud)

我在kotlin的解决方案:

class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {

    private val mDbHelper: TasksDbHelper

    init {
        checkNotNull(context)
        mDbHelper = TasksDbHelper(context)
    }

    companion object {
        lateinit var INSTANCE: TasksLocalDataSource
        private val initialized = AtomicBoolean()

        fun getInstance(context: Context) : TasksLocalDataSource {
            if(initialized.getAndSet(true)) {
                INSTANCE = TasksLocalDataSource(context)
            }
            return INSTANCE
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我错过了什么吗?线程安全?懒惰?

有一些类似的问题,但我不喜欢答案:)

fal*_*fal 81

这是Google的架构组件示例代码的一个简洁替代方案,它使用以下also函数:

class UsersDatabase : RoomDatabase() {

    companion object {

        @Volatile private var INSTANCE: UsersDatabase? = null

        fun getInstance(context: Context): UsersDatabase =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
            }

        private fun buildDatabase(context: Context) =
            Room.databaseBuilder(context.applicationContext,
                    UsersDatabase::class.java, "Sample.db")
                    .build()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @SandipSoni了解双重检查锁定:/sf/ask/1266561481/ (5认同)
  • 我不确定为什么有 INSTANCE ?: 同步块内?因为只有当 INSTANCE 为空时才会调用该块,所以为什么要再次检查 INSTANCE 是否为空?有人可以解释一下吗? (2认同)
  • 我发现这段代码有两个问题。1. 您始终必须传递上下文(或定义的任何其他变量),这在您想要检索单例时可能不可用。 2. 假设您在第二次使用时传递不同的上下文。那是什么意思?你只会得到旧的单身人士。对我来说似乎是一种反模式。 (2认同)
  • @A1m关于2):第二次传递不同的上下文并不重要,因为应用程序上下文用于构造数据库。您传递的“Context”对象仅用于检索应用程序上下文。 (2认同)
  • @MathiasBrandt 在这种情况下可能是正确的,谈论“Context”本身应该是一个单例。但问题通常询问创建此单例的模式。我觉得这种行为是不确定的并且具有误导性。 (2认同)

vod*_*dan 18

我不完全确定你为什么需要这样的代码,但这是我最好的镜头:

class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
    private val mDbHelper = TasksDbHelper(context)

    companion object {
        private var instance : TasksLocalDataSource? = null

        fun  getInstance(context: Context): TasksLocalDataSource {
            if (instance == null)  // NOT thread safe!
                instance = TasksLocalDataSource(context)

            return instance!!
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这与您编写的内容类似,并且具有相同的API.

几点说明:

  • 不要lateinit在这里使用.它有不同的用途,可以在这里使用可空的变量.

  • 怎么checkNotNull(context)办?context在这里永远不会是空的,这是Kotlin的保证.所有检查和断言都已由编译器实现.

更新:

如果您只需要一个懒惰的初始化类实例TasksLocalDataSource,那么只需使用一堆惰性属性(在对象内或包级别上):

val context = ....

val dataSource by lazy {
    TasksLocalDataSource(context)
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,此解决方案**不是线程安全的**.如果多个线程试图同时访问它,则可能存在两个单例实例并进行初始化. (2认同)

ami*_*phy 12

Thread-Safe SolutionWrite Once; Use Many;

可以创建一个实现单例逻辑的类,该类也包含单例实例。它使用同步块中的Double-Check Locking实例化实例,以消除在多线程环境中出现竞争状况的可能性。

SingletonHolder.kt

open class SingletonHolder<out T, in A>(private val constructor: (A) -> T) {

    @Volatile
    private var instance: T? = null

    fun getInstance(arg: A): T {
        return when {
            instance != null -> instance!!
            else -> synchronized(this) {
                if (instance == null) instance = constructor(arg)
                instance!!
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Usage

现在在每个应该为单例的类中,在类companion object之上编写一个扩展。SingletonHolder是一个通用类,它接受目标类的类型及其要求的参数作为通用参数。它还需要引用用于实例化的目标类的构造函数:

class MyManager private constructor(context: Context) {

    fun doSomething() {
        ...
    }

    companion object : SingletonHolder<MyManager, Context>(::MyManager)
}
Run Code Online (Sandbox Code Playgroud)

最后:

MyManager.getInstance(context).doSomething()
Run Code Online (Sandbox Code Playgroud)

  • 假设MyManger需要多个构造函数参数,如何用SingletonHolder处理 (4认同)
  • @AbhiMuktheeswarar 对 A 使用 Pair。然后:伴随对象: SingletonHolder&lt;MyManager,Pair&lt;A,B&gt;&gt; { MyManager(it.first,it.second) } (2认同)

Yis*_*ood 6

您可以声明一个 Kotlin 对象,重载 "invoke" operator

object TasksLocalDataSource: TasksDataSource {
    private lateinit var mDbHelper: TasksDbHelper

    operator fun invoke(context: Context): TasksLocalDataSource {
        this.mDbHelper = TasksDbHelper(context)
        return this
    }
}
Run Code Online (Sandbox Code Playgroud)

无论如何,我认为您应该将 TasksDbHelper 注入 TasksLocalDataSource 而不是注入 Context

  • 但它是线程安全的吗?我看到很多人称赞[这个](/sf/answers/3750659671/),这实际上是借用[这个2yo流行媒体帖子](https://medium.com/@BladeCoder/ kotlin-singletons-with-argument-194ef06edd9e),正如作者所说,它再次借用了 Kotlin“by懒惰”源本身。这个答案看起来更干净(我讨厌添加样板 util 文件,更不用说类了),但我离题了在多线程场景中使用这个单例。 (3认同)

Lea*_*rja 5

如果你想以更简单的方式将参数传递给单例,我认为这更好更短

object SingletonConfig {

private var retrofit: Retrofit? = null
private const val URL_BASE = "https://jsonplaceholder.typicode.com/"

fun Service(context: Context): Retrofit? {
    if (retrofit == null) {
        retrofit = Retrofit.Builder().baseUrl(URL_BASE)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    }
    return retrofit
}
Run Code Online (Sandbox Code Playgroud)

}

你用这种简单的方式调用它

val api = SingletonConfig.Service(this)?.create(Api::class.java)
Run Code Online (Sandbox Code Playgroud)


Gio*_*gos 5

该方法synchronized()在通用标准库中被标记为已弃用,因此替代方法是:

class MySingleton private constructor(private val param: String) {

    companion object {
        @Volatile
        private var INSTANCE: MySingleton? = null

        @Synchronized
        fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
    }
}
Run Code Online (Sandbox Code Playgroud)