> Можно ли создать Singleton с параметризованным конструктором в Kotlin (Kotlin, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Ozon

Стек: Kotlin, Android

> Пример ответа

Короткий ответ

Да, можно, но в Kotlin нет прямого синтаксиса для Singleton с параметризованным конструктором. Стандартный object не принимает параметры. Решение - использовать companion object с ленивой инициализацией и synchronized или LazyThreadSafetyMode.SYNCHRONIZED. Параметры передаются через метод getInstance(...), который создаёт экземпляр при первом вызове. Это классический паттерн Double-Checked Locking, адаптированный под Kotlin.

Подробное объяснение

В Kotlin object создаёт Singleton без параметров - конструктор всегда пустой. Если нужны параметры, приходится вручную управлять жизненным циклом. Основная проблема - потокобезопасность и гарантия единственности экземпляра при конкурентном доступе.

Подход: приватный конструктор с параметрами, companion object хранит nullable-ссылку на экземпляр, метод getInstance проверяет наличие, при отсутствии - создаёт. Для потокобезопасности используют synchronized на блокировке или lazy с режимом SYNCHRONIZED.

Важный нюанс: если параметры различаются между вызовами, Singleton теряет смысл - первый набор параметров "замораживается". Нужно либо документировать это поведение, либо использовать фабрику вместо Singleton.

На практике

В Android такой Singleton часто применяется для репозиториев или менеджеров, которым нужен Context или ApiService. Типичный пример - DatabaseManager(context). Но лучше использовать DI (Dagger, Hilt), который решает эту задачу чище. Если DI недоступен, паттерн оправдан.

Практические рекомендации:

  • параметры передавать один раз, при первом вызове;
  • не хранить Activity-контекст - только application context;
  • для тестируемости добавить метод resetForTest().

Пример кода

class DatabaseManager private constructor(
    private val context: Context,
    private val dbName: String
) {
    fun query(sql: String): List<Map<String, Any>> = // ...

    companion object {
        @Volatile
        private var instance: DatabaseManager? = null

        fun getInstance(context: Context, dbName: String): DatabaseManager {
            return instance ?: synchronized(this) {
                instance ?: DatabaseManager(
                    context.applicationContext,
                    dbName
                ).also { instance = it }
            }
        }
    }
}

Альтернатива через lazy:

companion object {
    private val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        // параметры нужно получить откуда-то заранее
        DatabaseManager(appContext, "default.db")
    }
}

Как отвечать на собеседовании

Начни с прямого ответа: "Да, но не через object". Затем объясни, почему object не подходит - отсутствие параметров. Покажи решение через companion object и synchronized. Упомяни @Volatile для видимости поля между потоками. Сравни с lazy - когда что выбирать. Обязательно скажи про ограничение: параметры фиксируются при первом вызове. В конце добавь, что в Android предпочтительнее DI, а Singleton - fallback.

Что проверяет интервьюер

  • понимание ограничений object в Kotlin;
  • знание паттерна Double-Checked Locking;
  • осознание проблем потокобезопасности;
  • умение объяснить trade-off между Singleton и DI;
  • способность видеть границы применимости паттерна.

Типичные ошибки

  • использование object с параметрами - синтаксически невозможно;
  • отсутствие @Volatile - возможна гонка потоков;
  • передача Activity-контекста - утечка памяти;
  • создание нового экземпляра при каждом вызове с другими параметрами - нарушение Singleton;
  • игнорирование вопроса о тестируемости - Singleton сложно мокать.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью