it-swarm.com.de

Wie implementiere ich ein Builder-Pattern in Kotlin?

Hallo, ich bin ein Neuling in der Kotlin-Welt. Ich mag das, was ich bisher sehe, und begann zu überlegen, einige unserer Bibliotheken, die wir in unserer Anwendung verwenden, von Java nach Kotlin zu konvertieren.

Diese Bibliotheken sind voll von Pojos mit Setter, Getter und Builder-Klassen. Jetzt habe ich gegoogelt, um herauszufinden, wie Builder in Kotlin am besten implementiert werden kann, jedoch kein Erfolg.

2. Update: Die Frage ist, wie man ein Builder-Entwurfsmuster für ein einfaches Pojo mit einigen Parametern in Kotlin schreibt. Der folgende Code ist mein Versuch, Java-Code zu schreiben und dann das Eclipse-Kotlin-Plugin zu verwenden, um nach Kotlin zu konvertieren.

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
        }
    }
}
98
Keyhan

In erster Linie müssen Sie in Kotlin in den meisten Fällen keine Builder verwenden, da wir Standard- und benannte Argumente haben. Dadurch können Sie schreiben

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

und benutze es wie folgt:

val car = Car(model = "X")

Wenn Sie unbedingt Builder verwenden möchten, können Sie Folgendes tun:

Den Builder zu einem companion object zu machen ist nicht sinnvoll, da objects Singletons sind. Deklarieren Sie sie stattdessen als verschachtelte Klasse (die in Kotlin standardmäßig statisch ist).

Verschieben Sie die Eigenschaften in den Konstruktor, sodass das Objekt auch auf normale Weise instanziiert werden kann (wenn dies nicht der Fall ist, machen Sie den Konstruktor privat), und verwenden Sie einen sekundären Konstruktor, der einen Builder verwendet und an den primären Konstruktor delegiert. Der Code sieht wie folgt aus:

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

Verwendung: val car = Car.Builder().model("X").build()

Dieser Code kann zusätzlich mit einem builder DSL gekürzt werden:

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

Verwendung: val car = Car.build { model = "X" }

Wenn einige Werte erforderlich sind und keine Standardwerte enthalten, müssen Sie sie in den Konstruktor des Builders und auch in die gerade definierte build-Methode einfügen:

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

Verwendung: val car = Car.build(required = "requiredValue") { model = "X" }

201
Kirill Rakhman

Da ich die Jackson-Bibliothek zum Analysieren von Objekten aus JSON verwende, muss ich einen leeren Konstruktor haben und keine optionalen Felder haben. Auch müssen alle Felder veränderbar sein. Dann kann ich diese Nice-Syntax verwenden, die dasselbe macht wie das Builder-Pattern:

val car = Car().apply{ model = "Ford"; year = 2000 }
10
David Vávra

Ich persönlich habe noch nie einen Baumeister in Kotlin gesehen, aber vielleicht bin ich es nur.

Alle erforderlichen Validierungen finden im init-Block statt:

class Car(val model: String,
          val year: Int = 2000) {

    init {
        if(year < 1900) throw Exception("...")
    }
}

Hier erlaubte ich mir zu erraten, dass Sie nicht wirklich wollen, dass model und year veränderbar sind. Auch diese Standardwerte scheinen keinen Sinn zu haben (insbesondere null für name), aber ich habe einen zu Demonstrationszwecken gelassen.

Eine Meinung: Das in Java verwendete Builder-Muster als Mittel zum Leben ohne benannte Parameter. In Sprachen mit benannten Parametern (wie Kotlin oder Python) sollten Konstruktoren mit langen Listen von (möglicherweise optionalen) Parametern verwendet werden.

7
voddan

Ich habe viele Beispiele gesehen, die den Bauherren zusätzlichen Spaß machen. Ich persönlich mag diesen Ansatz. Sparen Sie Aufwand beim Erstellen von Buildern.

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

    }
}

Ich habe noch keinen Weg gefunden, wie einige Felder in DSL initialisiert werden können, z. B. dass Fehler angezeigt werden, anstatt Ausnahmen zu werfen. Lass es mich wissen, wenn jemand weiß.

4
Arst

Ein Ansatz ist, wie folgt vorzugehen:

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

Anwendungsbeispiel:

val car = Car.Builder()
  .model("Ford Focus")
  .color("Black")
  .type("Type")
  .build()
4
Dmitrii Bychkov

Ich bin zu spät zur Party. Ich bin auch auf das gleiche Dilemma gestoßen, wenn ich das Builder-Muster im Projekt verwenden musste. Später, nach Recherche habe ich festgestellt, dass dies absolut unnötig ist, da Kotlin bereits die genannten Argumente und Standardargumente bereitstellt.

Wenn Sie wirklich implementieren müssen, ist die Antwort von Kirill Rakhman eine solide Antwort darauf, wie Sie am effektivsten implementieren können. Eine andere Sache, die Sie vielleicht nützlich finden, ist https://www.baeldung.com/kotlin-builder-pattern Sie können Java und Kotlin bezüglich ihrer Implementierung vergleichen und gegenüberstellen

1

Für eine einfache Klasse benötigen Sie keinen separaten Builder. Sie können optionale Konstruktorargumente verwenden, wie es von Kirill Rakhman beschrieben wurde.

Wenn Sie eine komplexere Klasse haben, bietet Kotlin eine Möglichkeit, Builders/DSL für den Groovy-Stil zu erstellen:

Typensichere Builder

Hier ist ein Beispiel:

Github-Beispiel - Builder/Assembler

1

Ich habe ein grundlegendes Builder-Muster mit folgendem Code in Kotlin implementiert:

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

Und schlussendlich 

Java:

new DialogMessage.Builder( context )
       .title("Title")
       .message("Message")
       .build()
       .show();

Kotlin:

DialogMessage.Builder( context )
       .title("Title")
       .message("")
       .build()
       .show()
0
Moises Portillo

Heutzutage sollten Leute die Type-Safe Builders von Kotlin überprüfen.

Die Verwendung der besagten Art der Objekterstellung sieht etwa so aus:

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ...
}

Ein schönes Anwendungsbeispiel von Nice ist das vaadin-on-kotlin - Framework, das mit typsicheren Buildern Ansichten und Komponenten zusammenstellt.

0
danield

Ich würde sagen, das Muster und die Implementierung bleiben in Kotlin ziemlich gleich. Sie können es manchmal aufgrund von Standardwerten überspringen, aber bei komplizierteren Objekterstellungen sind Builder immer noch ein nützliches Werkzeug, das nicht weggelassen werden kann.

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

    }

}
0
Brandon Rude

sie können optionale Parameter in kotlin verwenden. Beispiel:

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

dann 

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

Ich arbeitete an einem Kotlin-Projekt, das eine von Java-Clients verwendete API enthüllte (die die Kotlin-Sprachkonstrukte nicht nutzen kann). Wir mussten Builder hinzufügen, um sie in Java nutzbar zu machen. Deshalb habe ich eine @ Builder-Annotation erstellt: https://github.com/ThinkingLogic/kotlin-builder-annotation - Dies ist im Grunde ein Ersatz für die Lombok @ Builder-Annotation für Kotlin.

0
YetAnotherMatt