Как реализовать шаблон Builder в Котлине?
Привет, я новичок в мире Котлин. Мне нравится то, что я вижу до сих пор, и начал думать, чтобы преобразовать некоторые из наших библиотек, которые мы используем в нашем приложении от Java к Kotlin.
Эти библиотеки полны Pojos с сеттерами, геттерами и классами Builder. Теперь я нашел googled, чтобы найти лучший способ реализовать Builders в Kotlin, но не успел.
2nd Update: Вопрос заключается в том, как написать шаблон проектирования 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
}
}
}
Ответы
Ответ 1
Прежде всего, в большинстве случаев вам не нужно использовать компоновщики в Kotlin, потому что у нас есть аргументы по умолчанию и именованные аргументы. Это позволяет вам писать
class Car(val model: String? = null, val year: Int = 0)
и использовать это так:
val car = Car(model = "X")
Если вы абсолютно хотите использовать сборщики, вот как вы можете это сделать:
Делать Builder companion object
не имеет смысла, потому что object
- это одиночка. Вместо этого объявите его как вложенный класс (который является статическим по умолчанию в Kotlin).
Переместите свойства в конструктор, чтобы экземпляр объекта можно было создавать обычным способом (сделайте конструктор частным, если это не нужно) и используйте вторичный конструктор, который принимает конструктор и делегирует первичному конструктору. Код будет выглядеть следующим образом:
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").build()
Этот код может быть сокращен дополнительно с помощью построителя 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" }
Ответ 2
Поскольку я использую библиотеку Джексона для разбора объектов из JSON, мне нужно иметь пустой конструктор, и я не могу иметь необязательные поля. Также все поля должны быть изменены. Тогда я могу использовать этот красивый синтаксис, который делает то же самое, что и шаблон Builder:
val car = Car().apply{ model = "Ford"; year = 2000 }
Ответ 3
Я лично никогда не видел строителя в Котлине, но, возможно, это только я.
В блоке init
необходимо выполнить все проверки:
class Car(val model: String,
val year: Int = 2000) {
init {
if(year < 1900) throw Exception("...")
}
}
Здесь я позволил предположить, что вы действительно не хотели, чтобы model
и year
были изменены. Также значения по умолчанию, похоже, не имеют смысла (особенно null
для name
), но я оставил их для демонстрационных целей.
Мнение:
Шаблон построителя, используемый в Java, как средство жить без именованных параметров. В языках с именованными параметрами (например, Kotlin или Python) хорошей практикой является создание конструкторов с длинными списками (возможно, необязательных) параметров.
Ответ 4
Один из подходов - сделать что-то вроде следующего:
class Car(
val model: String?,
val color: String?,
val type: String?) {
data class Builder(
var model: String? = null,
var color: String? = null,
var type: String? = null) {
fun model(model: String) = apply { this.model = model }
fun color(color: String) = apply { this.color = color }
fun type(type: String) = apply { this.type = type }
fun build() = Car(model, color, type)
}
}
Образец использования:
val car = Car.Builder()
.model("Ford Focus")
.color("Black")
.type("Type")
.build()
Ответ 5
Я видел много примеров, которые объявляют дополнительные забавы как строители. Мне лично нравится такой подход. Сохраните усилия, чтобы писать строители.
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, например, показывать ошибки, а не бросать исключения. Дайте мне знать, если кто-нибудь знает.
Ответ 6
Для простого класса вам не нужен отдельный строитель. Вы можете использовать необязательные аргументы конструктора, как описал Кирилл Рахман.
Если у вас более сложный класс, то Kotlin предоставляет способ создания Groovy в стиле Builders/DSL:
Тип-Сейф Строители
Вот пример:
Пример Github - Строитель/Ассемблер
Ответ 7
Я бы сказал, что модель и реализация в Котлине остаются практически такими же. Вы можете иногда пропускать его по умолчанию, но для более сложного создания объектов строители по-прежнему являются полезным инструментом, который нельзя опустить.
Ответ 8
вы можете использовать необязательный параметр в 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")
Ответ 9
class Foo private constructor(@DrawableRes requiredImageRes: Int, optionalTitle: String?) {
@DrawableRes
@get:DrawableRes
val requiredImageRes: Int
val optionalTitle: String?
init {
this.requiredImageRes = requiredImageRes
this.requiredImageRes = optionalTitle
}
class Builder {
@DrawableRes
private var requiredImageRes: Int = -1
private var optionalTitle: String? = null
fun requiredImageRes(@DrawableRes imageRes: Int): Builder {
this.intent = intent
return this
}
fun optionalTitle(title: String): Builder {
this.optionalTitle = title
return this
}
fun build(): Foo {
if(requiredImageRes == -1) {
throw IllegalStateException("No image res provided")
}
return Foo(this.requiredImageRes, this.optionalTitle)
}
}
}
Ответ 10
Люди сегодня должны проверить Kotlin Type-Safe Builders.
Использование указанного способа создания объекта будет выглядеть примерно так:
html {
head {
title {+"XML encoding with Kotlin"}
}
// ...
}
Хороший пример использования "в действии" - это фреймворк vaadin-on-kotlin, в котором используются сборщики безопасных типов для сборки представлений и компонентов.
Ответ 11
Я реализовал базовый шаблон Builder в Kotlin с помощью следующего кода:
data class DialogMessage(
var title: String = "",
var message: String = ""
) {
class Builder( context: Context){
private var context: Context = context
private var title: String = ""
private var message: String = ""
fun title( title : String) = apply { this.title = title }
fun message( message : String ) = apply { this.message = message }
fun build() = KeyoDialogMessage(
title,
message
)
}
private lateinit var dialog : Dialog
fun show(){
this.dialog= Dialog(context)
.
.
.
dialog.show()
}
fun hide(){
if( this.dialog != null){
this.dialog.dismiss()
}
}
}
И наконец
Джава:
new DialogMessage.Builder( context )
.title("Title")
.message("Message")
.build()
.show();
Котлин:
DialogMessage.Builder( context )
.title("Title")
.message("")
.build()
.show()
Ответ 12
Я работал над проектом Kotlin, в котором был представлен API, используемый клиентами Java (которые не могут использовать языковые конструкции Kotlin). Нам пришлось добавить компоновщики, чтобы сделать их пригодными для использования в Java, поэтому я создал аннотацию @Builder: https://github.com/ThinkingLogic/kotlin-builder-annotation - это в основном замена аннотации Lombok @Builder для Kotlin.
Ответ 13
Я опоздал на вечеринку. Я также столкнулся с той же дилеммой, если мне пришлось использовать шаблон Builder в проекте. Позже, после исследования я понял, что это абсолютно не нужно, поскольку Kotlin уже предоставляет именованные аргументы и аргументы по умолчанию.
Если вам действительно нужно реализовать, ответ Кирилла Рахмана - это надежный ответ о том, как реализовать наиболее эффективно. Еще одна вещь, которая может оказаться вам полезной, - это https://www.baeldung.com/kotlin-builder-pattern, которую вы можете сравнить и сопоставить с Java и Kotlin по их реализации.