Начинается цикл статей, которые будут посвящены языку Kotlin:

Steps going down by Chance Agrella

После анонсирования Kotlin‘а как официального языка для разработки под Android все больше и больше разработчиков стали использоватьэтот язык в своих проектах.

Kotlin using statistics

Это обусловлено, в первую очередь, тем, что Kotlin - это что-то свежее, в отличие от Java 1.7. Например, в Koltin есть lambda:

val sum = { a, b -> a + b}
println(sum(1, 2)) // 3

А еще есть функции расширения, дата классы, делегирование, null safety, даже перегрузка операторов и еще много разного и интересного в стандартной библиотеке.

С одной стороны, свежий язык, про который многие говорят: “Я больше не буду писать на Java, ведь есть Kotlin”, а с другой стороны огромная потеря в поддержке кода и в масштабировании. Не верите? Начнем с расширений.

Расширения

Расширение - это функция, которую можно вписать в любой класс. Например, у класса java.util.List явно не хватает метода first, который возвращал бы первый элемент списка. Как эта проблема решена в Java? Там есть Guava, в которой полно статических методов, и один из них решает нашу задачу:

final List<String> words = ...
final String first = Iterables.getFirst(words, "");

Но теперь, когда у нас есть Kotlin, мы можем решить эту задачу с помощью… барабанная дробь… статического метода

fun <T> List<T>.first(): T {
    return this.get(0)
}

(такой метод даже в стандартной библиотеке есть). Да, функции расширения в Koltin - это просто статические методы. Если метод выше декомпилировать в Java, то получится примерно следующее:

public static final Object first(@NotNull List $receiver) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    return $receiver.get(0);
}

То есть функции расширения дают возможность использовать утилитарные методы иначе:

val words: List<String> = ...
val first = words.first()

Рассмотрим жизненный пример: почтовый клиент. Клиент написан давно, вы работаете над программой две недели и вам дают задачу: считать количество слов при отображении письма и выводить это количество в информационной строке. Вы находите интерфейс Post, и пишите для него метод расширение (в другом файле):

interface Post {
    fun body(): String
}
fun Post.wordsCount(): Int {
    val text = this.body()
    ...
}

Начинаете тестировать новый метод и видите, что подсчет слов работает неправильно, он считает на два слова больше, чем нужно. Оказывается, есть декоратор над Post - HtmlPost, который форматирует текст письма в соответствии с заданными настройками цветов, так вот он добавляет два тега - открывающий и закрывающий к телу письма:

class HtmlPost {
    override fun body(): String {
        return "<post> ${origin.body()} </post>"
    }
}

Что же делать? Если написать расширение для HtmlPost (это все равно, что другой статический метод в Utility классе), то либо будет дублирование кода, потому что уже написан код для подсчета слов, нужно только теги игнорировать, либо из одного статического метода будет вызываться другой статический метод и появятся проблемы с тестированием.

Другая проблема - постоянный вопрос при написании метода расширения: В каком файле писать extension функцию? Еще похожий вопрос: А, может, уже написана эта функция? А если написана похожая, и не подходит для решения текущей задачи? Дублировать или пытаться расширять и изменять? Когда дело касается утилитных методов, то все, что было в ООП становится неприменимым: декораторы, фабрики, композиции - это невозможно сделать со статическими методами.


Этот пример показывает, что функции расширения в Kotlin имеют те же проблемы, что и статические методы в Utility классах. То есть, другими словами, используя расширения придется больше времени тратить на поддержку кода. В следующей статье рассмотрим, куда приведет использование делегирования.