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