Мы так привыкли к Dependency Injection (Dagger/Hilt/Koin), что перестали замечать, как он раздувает наш код.
Сценарий из жизни: вам нужно залогировать событие глубоко в слое бизнес-логики. Вы берете интерфейс
Logger и прокидываете его через конструкторы пяти промежуточных классов, хотя он нужен только в одном методе на самом дне.В Kotlin 2.1+ (KEEP-367) активно допиливают киллер-фичу, которая меняет правила игры - Context Parameters.
Историческая справка: Если вы помните экспериментальные Context Receivers (синтаксис
context(Logger)), забудьте их. JetBrains официально задепрекейтили их в версии 2.0.20, заменив на более строгие и читаемые Context Parameters.Как это выглядит теперь?
Допустим, у нас есть
TransactionScope и Logger. Мы не хотим инжектить их в классы-прослойки, мы хотим потребовать их наличие в функции неявно.
// Новая магия: context(...) перед функцией с явными именами переменных
context(logger: Logger, tx: TransactionScope)
fun deleteUser(userId: String) {
logger.log("Deleting user $userId")
tx.execute {
// ... логика удаления в БД
}
}
В чем отличие от обычных параметров?
При вызове функции
deleteUser вам не нужно передавать эти параметры руками! Компилятор сам найдет их в текущем скоупе (scope) и подставит под капотом.
class UserUseCase(
private val logger: Logger,
private val tx: TransactionScope
) {
fun execute() {
// Вводим зависимости в область видимости
with(logger) {
with(tx) {
// Вызываем функцию БЕЗ передачи логгера и транзакции!
// Компилятор сам слинкует их из контекста.
deleteUser("user_123")
}
}
}
}
(На практике вы настроите скоуп один раз на самом верхнем уровне — например, в базовой
ViewModel или HTTP-хендлере Ktor — и вся вложенная цепочка вызовов получит к ним доступ).Почему это круто для архитектуры?
1. Чистые сигнатуры: Бизнес-функции принимают только бизнес-аргументы (например,
userId: String). Вся техническая инфраструктура (логирование, аналитика, права доступа, транзакции) выносится за скобки.2. Читаемость (в отличие от старых Receivers): Раньше методы из контекста вызывались неявно (просто
log()), и в большом классе было невозможно понять, откуда пришел этот метод. Теперь у нас есть жесткое имя: logger.log().3. Идеально для Cross-cutting concerns: Это элегантное решение для сквозного функционала, который не должен загрязнять доменную модель.
⚠️ Правило Сеньора:
Context Parameters - это не замена конструкторному DI.
Если классу нужен
UserRepository для выполнения всех его основных задач, передавайте его в конструктор. Используйте context(...) только для тех сущностей, которые меняются в зависимости от среды выполнения (call chain), или для утилитарных инструментов (например, AnalyticsScope).✍️ @kotlin_lib