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



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



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



2-е обновление: вопрос в том, как написать Дизайн-Шаблон 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
}
}
}
961   8  

8 ответов:

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

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

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

val car = Car(model = "X")

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

сделать сайт 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()

этот код может быть сокращен, кроме того, с помощью 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 пример:

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")

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

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

Типобезопасные Строители

вот пример:

Пример Github-Builder / Assembler

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)
        }

    }

}

Comments

    Ничего не найдено.