Синглтон с параметром в Котлине

Я пытаюсь преобразовать приложение 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
        }
    }
}

Я что-то пропустил? Безопасность резьбы? Лень?

Было несколько похожих вопросов, но мне не нравятся ответы:)

Ответ 1

Здесь аккуратная альтернатива из компонентов архитектуры 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()
    }
}

Ответ 2

Я не совсем уверен, зачем вам нужен такой код, но вот мой лучший снимок:

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

Это похоже на то, что вы написали, и имеет тот же API.

Несколько примечаний:

  • Не используйте lateinit здесь. Он имеет другую цель, и здесь может быть выбрана нулевая переменная.

  • Что делает checkNotNull(context)? context здесь никогда не является нулевым, это гарантируется Котлином. Все проверки и утверждения уже реализованы компилятором.

UPDATE:

Если вам нужен только лениво инициализированный экземпляр класса TasksLocalDataSource, тогда просто используйте кучу ленивых свойств (внутри объекта или на уровне пакета):

val context = ....

val dataSource by lazy {
    TasksLocalDataSource(context)
}

Ответ 3

Thread-Safe Solution # Write Once; Use Many; Write Once; Use Many;

Можно создать класс, реализующий логику singleton, которая также содержит экземпляр singleton. Он создает экземпляр с использованием двойной проверки блокировки в синхронизированном блоке, чтобы исключить возможность состояния гонки в многопоточных средах.

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 object расширяющийся над классом. SingletonHolder - это универсальный класс, который принимает тип целевого класса и его обязательный параметр в качестве универсальных параметров. Также необходима ссылка на конструктор целевого класса, который используется для создания экземпляра:

class MyManager private constructor(context: Context) {

    fun doSomething() {
        ...
    }

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

В заключение:

MyManager.getInstance(context).doSomething()

Ответ 4

если вы хотите передать параметр в singleton более простым способом, я думаю, что это лучше и короче

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)

Ответ 5

Вы можете объявить объект Kotlin, перегружая оператор "invoke".

object TasksLocalDataSource: TasksDataSource {
    private lateinit var mDbHelper: TasksDbHelper

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

В любом случае, я думаю, что вы должны вводить TasksDbHelper в TasksLocalDataSource вместо того, чтобы вводить контекст

Ответ 6

Если единственным параметром, который вам нужен, является Context приложения, то вы можете инициализировать его до верхнего уровня val, на ранней стадии ContentProvider, как это делает Firebase SDK.

Поскольку объявление ContentProvider немного громоздко, я создал библиотеку, которая предоставляет свойство верхнего уровня с именем appCtx для всех мест, где вам не нужен какой-либо Activity или другой специальный связанный с жизненным циклом контекст.

Ответ 7

решение с ленивым

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

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

Singletons

Синглтоны используются достаточно часто для более простого способа их создания. Вместо обычного статического экземпляра, метода getInstance() и частного конструктора, Kotlin использует обозначение объекта. Для согласованности объектная нотация также используется для определения статических методов.

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