Как реализовать шаблон Builder в Котлине?

Привет, я новичок в мире Котлин. Мне нравится то, что я вижу до сих пор, и начал думать, чтобы преобразовать некоторые из наших библиотек, которые мы используем в нашем приложении от Java к Kotlin.

Эти библиотеки полны Pojos с сеттерами, геттерами и классами Builder. Теперь у меня есть googled, чтобы найти, что является лучшим способом реализовать Builders в Kotlin, но не имеет успеха.

Второе обновление. Вопрос заключается в том, как написать шаблон проектирования Builder для простого pojo с некоторыми параметрами в Kotlin? Код ниже – моя попытка написать java-код, а затем использовать eclipse-kotlin-plugin для преобразования в Kotlin.

class Car private constructor(builder:Car.Builder) { var model:String? = null var year:Int = 0 init { this.model = builder.model this.year = builder.year } companion object Builder { var model:String? = null private set var year:Int = 0 private set fun model(model:String):Builder { this.model = model return this } fun year(year:Int):Builder { this.year = year return this } fun build():Car { val car = Car(this) return car } } } 

    Прежде всего, в большинстве случаев вам не нужно использовать строителей в Котлине, потому что у нас есть аргументы по умолчанию и именованные. Это позволяет вам писать

     class Car(val model: String? = null, val year: Int = 0) 

    и используйте его так:

     val car = Car(model = "X") 

    Если вы абсолютно хотите использовать строителей, вот как вы могли это сделать:

    Создание Builder companion object не имеет смысла, поскольку object s является одиночным. Вместо этого объявите его как вложенный класс (который по умолчанию статичен в Котлине).

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

     class Car( //add private constructor if necessary val model: String?, val year: Int ) { private constructor(builder: Builder) : this(builder.model, builder.year) class Builder { var model: String? = null private set var year: Int = 0 private set fun model(model: String) = apply { this.model = model } fun year(year: Int) = apply { this.year = year } fun build() = Car(this) } } 

    Использование: val car = Car.Builder().model("X").builder()

    Этот код можно укоротить дополнительно с помощью построителя DSL :

     class Car ( val model: String?, val year: Int ) { private constructor(builder: Builder) : this(builder.model, builder.year) companion object { inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build() } class Builder { var model: String? = null var year: Int = 0 fun build() = Car(this) } } 

    Использование: val car = Car.build { model = "X" }

    Если требуются некоторые значения и не имеют значений по умолчанию, вам необходимо поместить их в конструктор построителя, а также в метод build который мы только что определили:

     class Car ( val model: String?, val year: Int, val required: String ) { private constructor(builder: Builder) : this(builder.model, builder.year, builder.required) companion object { inline fun build(required: String, block: Builder.() -> Unit) = Builder(required).apply(block).build() } class Builder( val required: String ) { var model: String? = null var year: Int = 0 fun build() = Car(this) } } 

    Использование: val car = Car.build(required = "requiredValue") { model = "X" }

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

     val car = Car().apply{ model = "Ford"; year = 2000 } 

    Я лично никогда не видел строителя в Котлине, но, возможно, это только я.

    В блоке init необходимо выполнить все проверки:

     class Car(val model: String, val year: Int = 2000) { init { if(year < 1900) throw Exception("...") } } 

    Здесь я позволил предположить, что вы не хотите, чтобы model и year менялись. Также эти значения по умолчанию, похоже, не имеют смысла (особенно null для name ), но я оставил их для демонстрационных целей.

    Мнение: шаблон строителя, используемый в Java, как средство жить без названных параметров. В языках с именованными параметрами (например, Kotlin или Python) хорошей практикой является наличие конструкторов с длинными списками (возможно, необязательных) параметров.

    Я видел много примеров, которые объявляют дополнительные развлечения в качестве строителей. Мне лично нравится такой подход. Сохраните усилия, чтобы писать строителей.

     package android.zeroarst.lab.koltinlab import kotlin.properties.Delegates class Lab { companion object { @JvmStatic fun main(args: Array<String>) { val roy = Person { name = "Roy" age = 33 height = 173 single = true car { brand = "Tesla" model = "Model X" year = 2017 } car { brand = "Tesla" model = "Model S" year = 2018 } } println(roy) } class Person() { constructor(init: Person.() -> Unit) : this() { this.init() } var name: String by Delegates.notNull() var age: Int by Delegates.notNull() var height: Int by Delegates.notNull() var single: Boolean by Delegates.notNull() val cars: MutableList<Car> by lazy { arrayListOf<Car>() } override fun toString(): String { return "name=$name, age=$age, " + "height=$height, " + "single=${when (single) { true -> "looking for a girl friend T___T" false -> "Happy!!" }}\nCars: $cars" } } class Car() { var brand: String by Delegates.notNull() var model: String by Delegates.notNull() var year: Int by Delegates.notNull() override fun toString(): String { return "(brand=$brand, model=$model, year=$year)" } } fun Person.car(init: Car.() -> Unit): Unit { cars.add(Car().apply(init)) } } } 

    Я еще не нашел способ, который может заставить некоторые поля быть инициализированными в DSL, например, показывать ошибки, а не бросать исключения. Дай мне знать, если кто знает.

    Я бы сказал, что образец и реализация в Котлине остаются практически такими же. Иногда вы можете пропустить его благодаря значениям по умолчанию, но для более сложного создания объектов строители по-прежнему являются полезным инструментом, который нельзя опустить.

    Для простого класса вам не нужен отдельный строитель. Вы можете использовать необязательные аргументы конструктора, как описано Кирилл Рахман.

    Если у вас более сложный класс, то Kotlin предоставляет способ создания Groovy style Builders / DSL:

    https://kotlinlang.org/docs/reference/type-safe-builders.html

    Вот пример:

    https://github.com/dbacinski/Design-Patterns-In-Kotlin#builder–assembler

    вы можете использовать необязательный параметр в примере kotlin:

     fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String = "default") { System.out.printf("parameter %s %d %d %s\n", p1, p2, p3, p4) } 

    тогда

     myFunc("a") myFunc("a", 1) myFunc("a", 1, 2) myFunc("a", 1, 2, "b")