Почему не использовать статические классы вместо функций расширения в Kotlin
Что проверяют
Знаете ли вы, во что компилируется функция расширения, и понимаете ли её ограничения. Ответа «так красивее» мало: следом спрашивают, можно ли переопределить расширение в наследнике и что вызовется, если у класса появится метод с такой же сигнатурой.
Как отвечать
Снимите ложное противопоставление: в Kotlin нет статических классов, их роль играют top-level функции, object и companion object. При этом функция расширения сама компилируется в статический метод, где получатель становится первым параметром. Разница не в байткоде, а в способе вызова и в области видимости.
Что даёт расширение:
- вызов по месту:
text.toSlug()вместоStringUtils.toSlug(text), автодополнение по типу показывает функцию там, где она нужна; - работа с чужими и финальными классами:
String,View, классы из библиотеки, которые вы не можете править. Всяandroidx.core.ktxустроена так; - область видимости через импорт: расширение видно только там, где импортировано, и не засоряет общее пространство имён, в отличие от публичного
object Utils.
Дальше ограничения, это и есть проверяемая часть:
- разрешение статическое, по объявленному типу переменной, а не по фактическому. Наследник расширение не переопределит;
- член класса всегда выигрывает у расширения с той же сигнатурой, и ваша функция тихо перестанет вызываться, если такой метод появится в библиотеке;
- расширение не видит
privateиprotectedчлены, доступен только публичный интерфейс; - расширение трудно подменить в тесте: обычный мок его не перехватит, нужен
mockkStatic.
Практический вывод: расширения для утилит без состояния и адаптации чужих типов, а поведение с зависимостями лучше держать в обычном классе за интерфейсом.
Где спотыкаются
Считают, что расширение добавляет метод в класс. Класс не меняется, это статическая функция с синтаксическим сахаром.
Ждут полиморфизма. open fun переопределяется, расширение нет: вызовется то, что компилятор видит по типу переменной.
Складывают в расширения бизнес-логику с зависимостями. Такой код нельзя ни подменить в тесте, ни снабдить зависимостью иначе, чем лишним параметром или глобальным объектом.