Следуя моей предыдущей статье: Шаблон Builder в Java, и с недавнего времени я стараюсь писать на Kotlin и максимально избегать Java (у меня нет каких-то строгих мнений против Java как такового. Фактически это был мой первый язык программирования, просто выразительность Kotlin мне нравится гораздо больше) Я хотел посмотреть, пригодится ли Kotlin в этом случае и как.

Кратко напомним, что шаблон Builder обеспечивает большую гибкость при построении сложных объектов, значительно улучшает читаемость кода (то, к чему я всегда стремлюсь), поддерживает неизменяемость классов и довольно хорошо масштабируется. Поэтому вместо вызова конструктора или такого класса (Java):

Car car = new Car("Ford", "Mustang", 1968, "blue", "fastback", 2, "manual", "V8");

мы вызываем методы построителя (разработанные с учетом плавного интерфейса):

Car car = new Car.Builder("Ford", "Mustang", 1968)
    .color("blue")
    .bodyType("fastback")
    .noOfDoors(2)
    .transmission("manual")
    .engine("V8")
    .build()

Можно объявить обязательные свойства в конструкторе Builder и установить остальные с помощью функций установки по мере необходимости или оставить их по умолчанию. У паттерна Builder есть некоторые минусы (подробнее см. Шаблон Builder в Java). Кроме того, цепочка вызовов методов может стать довольно большой в зависимости от количества свойств, которые мы хотим установить для класса.

Нужно ли нам применять те же принципы в Kotlin и по-прежнему использовать конструкторы? За то короткое время, что я пишу код на Kotlin, я обнаружил, что половину времени применяю техники, направленные на устранение недостатков и ограничений Java и к которым мы так привыкли, в Kotlin не нужны.

Сначала создадим тот же класс, что и выше, но на Kotlin:

data class Car(
    val make: String, // mandatory
    val model: String, // mandatory
    val year: Int, // mandatory
    val color: Blue, // optional
    val bodyType: String, // optional
    val noOfDoors: Int, // optional
    val transmission: String, // optional
    val engine: String // optional
)

Благодаря аргументам по умолчанию можно установить значения по умолчанию для необязательных свойств функции:

fun power(base: Int, power: Int = 2) = TODO()

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

power(8) // => 64
power(6, 3) // => 216

Мы можем сделать то же самое с классом Car и установить значения по умолчанию прямо в конструкторе:

data class Car(
    val make: String, // mandatory
    val model: String, // mandatory
    val year: Int, // mandatory
    val color: Blue = "", // optional
    val bodyType: String = "", // optional
    val noOfDoors: Int = 4, // optional
    val transmission: String = "", // optional
    val engine: String = "" // optional
)

и создайте класс только с обязательными свойствами:

val car = Car("Ford", "Mustang", 1968)

или переопределить те, которые нам нужны:

val car = Car("Ford", "Mustang", 1968, "blue", "fastback", 2)

Но это решает только половину проблемы. Что, если мы хотим предоставить только некоторые необязательные параметры без необходимости указывать их все? Для этого в Kotlin есть именованные аргументы, еще одна очень полезная функция, которой, к сожалению, не хватает в Java, которая позволяет нам предоставлять параметры функциям, используя их имена, например так:

val car = Car(
    make = "Ford", 
    model = "Mustang", 
    year = 1968, 
    color =  "blue", 
    bodyType = "fastback", 
    noOfDoors = 2, 
    transmission = "manual", 
    engine = "V8"
)

В сочетании с аргументами по умолчанию мы можем делать такие вещи:

val car = Car(
    make = "Ford", 
    model = "Mustang", 
    year = 1968, 
    engine = "V8"
)

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

Единственное ограничение по сравнению с шаблоном построителя заключается в том, что мы не можем отложить создание объекта на более позднее время, как в случае с Java-построителем:

CarBuilder carBuilder = new Car.Builder("Mercedes", "300 SLR" 1955)
    .color("silver");
// do something
Car car = carBuilder.build();

Для этого я напишу еще одну статью о том, как создавать сборщики в стиле DSL на Kotlin.