Следуя моей предыдущей статье: Шаблон 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.