在Kotlin中带参数的单例

时间:2016-11-03 09:36:21

标签: 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;
    }
}

我在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
        }
    }
}

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

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

12 个答案:

答案 0 :(得分:62)

这是Google的架构组件sample code的一个简洁替代方案,它使用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()
    }
}

答案 1 :(得分:17)

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

BackColor

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

一些注意事项:

  • 请勿在此处使用int sum = 0; for(int i = 1; i < N; i *= 2) for(int j =0; j <i; j++) sum++; 。它有不同的用途,可以在这里使用可空的变量。

  • int sum = 0; for(int i = 0; i < N; i *= 2) for(int j =0; j <i; j++) sum++; 做了什么? 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!! } } } 在这里永远不会为空,这是Kotlin的保证。所有检查和断言都已由编译器实现。

更新

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

checkNotNull(context)

答案 2 :(得分:6)

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!!
            }
        }
    }
}

Usage

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

class MyManager private constructor(context: Context) {

    fun doSomething() {
        ...
    }

    companion object : SingletonHolder<MyManager, Context>(::MyManager)
}

最后:

MyManager.getInstance(context).doSomething()

答案 3 :(得分:4)

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

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
}

}

您就可以轻松地调用它

val api = SingletonConfig.Service(this)?.create(Api::class.java)

答案 4 :(得分:2)

您可以声明Kotlin对象overloading "invoke" operator

object TasksLocalDataSource: TasksDataSource {
    private lateinit var mDbHelper: TasksDbHelper

    operator fun invoke(context: Context): TasksLocalDataSource {
        this.mDbHelper = TasksDbHelper(context)
        return this
    }
}

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

答案 5 :(得分:1)

如果您正在寻找具有多个参数的基本 SingletonHolder 类。我创建了 SingletonHolder 泛型类,该类支持仅创建带有一个参数、两个参数和三个参数的单例类的一个实例。

link Github of the base class here

非参数(Kotlin 的默认设置):

object AppRepository 

一个参数(来自上面链接中的示例代码):

class AppRepository private constructor(private val db: Database) {
    companion object : SingleArgSingletonHolder<AppRepository, Database>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db)

两个参数:

class AppRepository private constructor(private val db: Database, private val apiService: ApiService) {
    companion object : PairArgsSingletonHolder<AppRepository, Database, ApiService>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db, apiService)

三个参数:

class AppRepository private constructor(
   private val db: Database,
   private val apiService: ApiService,
   private val storage : Storage
) {
   companion object : TripleArgsSingletonHolder<AppRepository, Database, ApiService, Storage>(::AppRepository)
}
// Use
val appRepository =  AppRepository.getInstance(db, apiService, storage)

超过 3 个参数:

为了实现这种情况,我建议创建一个配置对象来传递给单例构造函数。

答案 6 :(得分:0)

如果您唯一需要的参数是应用程序Context,则可以像Firebase SDK一样在val的早期将其初始化为顶级ContentProvider

由于声明ContentProvider有点麻烦,I made a library that provides a top level property named appCtx对于不需要活动或其他特殊生命周期绑定上下文的所有地方。

答案 7 :(得分:0)

懒惰的解决方案

class LateInitLazy<T>(private var initializer: (() -> T)? = null) {

    val lazy = lazy { checkNotNull(initializer) { "lazy not initialized" }() }

    fun initOnce(factory: () -> T) {
        initializer = factory
        lazy.value
        initializer = null
    }
}

val myProxy = LateInitLazy<String>()
val myValue by myProxy.lazy

println(myValue) // error: java.lang.IllegalStateException: lazy not inited

myProxy.initOnce { "Hello World" }
println(myValue) // OK: output Hello World

myProxy.initOnce { "Never changed" } // no effect
println(myValue) // OK: output Hello World

答案 8 :(得分:0)

class CarsRepository(private val iDummyCarsDataSource: IDummyCarsDataSource) {

    companion object {
        private var INSTANCE: CarsRepository? = null
        fun getInstance(iDummyCarsDataSource: IDummyCarsDataSource): CarsRepository {
            if (INSTANCE == null) {
                INSTANCE = CarsRepository(
                    iDummyCarsDataSource = iDummyCarsDataSource)
            }
            return INSTANCE as CarsRepository
        }
    }

}

答案 9 :(得分:0)

方法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 }
    }
}

答案 10 :(得分:0)

我看到了所有答案。我知道这是一个重复的答案,但是如果我们在方法声明中使用synced关键字,它将把整个方法同步到对象或类。并且 synchronized 块尚未被弃用。

您可以使用以下实用程序类来获取单例行为。

open class SingletonWithContextCreator<out T : Any>(val creator: (Context) -> T) {
    @Volatile
    private var instance: T? = null

    fun with(context: Context): T = instance ?: synchronized(this) {
        instance ?: creator(context).apply { instance = this }
    }
}

您可以将上述类扩展为您要创建单例的任何类。

在您的情况下,以下是使 TasksLocalDataSource 类单例的代码。

companion object : SingletonWithContextCreator<TasksDataSource>(::TasksLocalDataSource)

答案 11 :(得分:-5)

Singletons

单身经常被用来创建它们的简单方法。 Kotlin使用对象表示法,而不是通常的静态实例,getInstance()方法和私有构造函数。 为了保持一致性,对象表示法也用于定义静态方法。

 object CommonApiConfig {
private var commonApiConfig: CommonApiConfig? = null
fun getInstance(): CommonApiConfig {
    if (null == commonApiConfig) {
        commonApiConfig = CommonApiConfig
       }
    return CommonApiConfig.commonApiConfig!!
   }
}