Kotlin'de Builder kalıbı nasıl uygulanır?


146

Merhaba ben Kotlin dünyasında bir acemi. Şimdiye kadar gördüklerimi beğendim ve uygulamamızda kullandığımız bazı kütüphanelerimizi Java'dan Kotlin'e dönüştürmeyi düşünmeye başladım.

Bu kütüphaneler, setterler, alıcılar ve Builder sınıflarıyla Pojos ile doludur. Şimdi Kotlin'de Builders'ı uygulamanın en iyi yolunu bulmaya çalıştım, ancak başarı elde etmedim.

2. Güncelleme: Soru, Kotlin'de bazı parametrelerle basit bir pojo için bir Builder tasarım deseni nasıl yazılır? Aşağıdaki kod, java kodu yazıp Kotlin'e dönüştürmek için eclipse-kotlin-eklentisini kullanarak denemem.

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
Eğer gerek yok modelve yeardeğişken olmak? Bir Caryaratımdan sonra bunları değiştiriyor musunuz ?
voddan

Sanırım değişmez olmalılar evet. Ayrıca her ikisinin de ayarlı olmadığından ve boş olmadığından emin olmak istersiniz
Keyhan

1
Bu github.com/jffiorillo/jvmbuilder Ek Açıklama İşlemcisini, oluşturucu sınıfını sizin için otomatik olarak oluşturmak için de kullanabilirsiniz .
JoseF

@JoseF Standart kotlin'e eklemek iyi bir fikir. Kotlin dilinde yazılmış kütüphaneler için kullanışlıdır.
Keyhan

Yanıtlar:


273

Her şeyden önce, çoğu durumda Kotlin'de inşaatçılar kullanmanıza gerek yoktur, çünkü varsayılan ve adlandırılmış argümanlarımız var. Bu yazmanıza olanak tanır

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

ve şu şekilde kullanın:

val car = Car(model = "X")

Kesinlikle inşaatçıları kullanmak istiyorsanız, bunu nasıl yapabileceğiniz aşağıda açıklanmıştır:

Builder'ı companion objectyapmak mantıklı değil çünkü objects tekton. Bunun yerine bunu iç içe bir sınıf olarak bildirir (Kotlin'de varsayılan olarak statiktir).

Nesnenin normal şekilde somutlaştırılabilmesi için yapıcıya özellikleri taşıyın (yapmaması gerekiyorsa yapıcıyı özel yapın) ve bir yapıcı ve delegeleri birincil kurucuya götüren ikincil bir kurucu kullanın. Kod aşağıdaki gibi görünecektir:

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

Kullanımı: val car = Car.Builder().model("X").build()

Bu kod, bir oluşturucu DSL kullanılarak ek olarak kısaltılabilir :

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

Kullanımı: val car = Car.build { model = "X" }

Bazı değerler gerekliyse ve varsayılan değerlere sahip değilseniz, bunları oluşturucunun yapıcısına ve ayrıca buildaz önce tanımladığımız yönteme koymanız gerekir :

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

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


2
Hiçbir şey, ancak sorunun yazarı özellikle oluşturucu deseninin nasıl uygulanacağını sordu.
Kirill Rakhman

4
Kendimi düzeltmeliyim, oluşturucu deseninin bazı avantajları var, örneğin kısmen inşa edilmiş bir yapıcıyı başka bir yönteme geçirebilirsiniz. Ama haklısın, bir açıklama ekleyeceğim.
Kirill Rakhman

3
@KirillRakhman Oluşturucuyu Java'dan çağırmaya ne dersiniz? Oluşturucuyu java için kullanılabilir hale getirmenin kolay bir yolu var mı?
Keyhan

6
Java çağrılabilir Her üç sürümleri bu yüzden istiyorum: Car.Builder builder = new Car.Builder();. Ancak yalnızca ilk sürümün akıcı bir arayüzü vardır, bu nedenle ikinci ve üçüncü sürümlere yapılan çağrılar zincirlenemez.
Kirill Rakhman

10
Bence üstteki kotlin örneği sadece bir olası kullanım örneğini açıklıyor. İnşaatçıları kullanmamın temel nedeni, değişebilir bir nesneyi değişmez bir nesneye dönüştürmektir. Yani, "inşa ederken" zamanla mutasyona uğramam ve sonra değişmez bir nesne bulmam gerekiyor. En azından benim kodumda, birçok farklı kurucu yerine bir oluşturucu kullanacağım parametrelerin birçok varyasyonu olan kodun sadece bir veya 2 örneği var. Ama değişmez bir nesne yapmak için, bir inşaatçının kesinlikle düşünebileceğim en temiz yol olduğu birkaç durumum var.
ycomp

21

Bir yaklaşım aşağıdaki gibi bir şey yapmaktır:

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

Kullanım örneği:

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

Çok teşekkürler! Günümü gün ettin! Cevabınız ÇÖZÜM olarak işaretlenmelidir.
sVd

9

JSON'dan nesneleri ayrıştırmak için Jackson kütüphanesini kullandığım için boş bir yapıcıya ihtiyacım var ve isteğe bağlı alanlara sahip olamam. Ayrıca tüm alanlar değiştirilebilir olmalıdır. Sonra Builder desen ile aynı şeyi yapan bu güzel sözdizimini kullanabilirsiniz:

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

8
Jackson'da aslında boş bir kurucuya ihtiyacınız yoktur ve alanların değişebilir olması gerekmez. Yapıcı parametrelerine sadece@JsonProperty
Bastian Voigt

2
Anahtarla derlerseniz, @JsonPropertyartık açıklama eklemenize bile gerek yoktur -parameters.
Amir Abiri

2
Jackson aslında bir inşaatçı kullanacak şekilde yapılandırılabilir.
Keyhan

1
Eğer projenize jackson-module-kotlin modülünü eklerseniz, sadece veri sınıflarını kullanabilirsiniz.
Nils Breunese

2
Bu, bir Oluşturucu Kalıbı ile aynı şeyi nasıl yapıyor? Nihai ürünü başlatır ve sonra değiştirir / bilgi eklersiniz. Builder deseninin tüm amacı, gerekli tüm bilgiler elde edilene kadar nihai ürünü alamamaktır. .Apply () yönteminin kaldırılması, tanımsız bir araç bırakır. Tüm yapıcı argümanlarını Builder'dan kaldırmak, sizi bir Araba Oluşturucu ile bırakır ve bunu bir arabaya kurmaya çalışırsanız, modeli ve yılı henüz belirtmediğiniz için bir istisnaya girersiniz. Aynı şey değiller.
ZeroStatic

7

Şahsen Kotlin'de hiç bir inşaatçı görmedim, ama belki sadece benim.

İhtiyaç duyulan tüm doğrulama initblokta gerçekleşir:

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

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

Burada gerçekten istemediğini tahmin etmek için bir özgürlük aldım model ve yeardeğiştirilebilir olmak . Ayrıca bu varsayılan değerlerin hiçbir anlamı yok gibi görünüyor (özellikle nulliçin name) ama gösteri amaçlı bir tane bıraktım.

Bir Fikir: Java'da adlandırılmış parametreler olmadan yaşamak için kullanılan oluşturucu deseni. İsimlendirilmiş parametrelere sahip dillerde (Kotlin veya Python gibi), uzun (belki isteğe bağlı) parametre listelerine sahip yapıcılara sahip olmak iyi bir uygulamadır.


2
Cevabınız için çok teşekkürler. Yaklaşımınızı seviyorum ama olumsuz, birçok parametreye sahip bir sınıf için yapıcıyı kullanmak ve sınıfı test etmek o kadar kolay olmaz.
Keyhan

1
+ Alanlar arasında doğrulamanın gerçekleşmediğini varsayarak doğrulama yapabilmenin diğer iki yolu: 1) ayarlayıcının doğrulama yaptığı özellik delegelerini kullanın - bu, doğrulama yapan normal bir ayarlayıcıya sahip olmakla aynı şeydir 2) Kaçının ilkel saplantılar ve kendilerini doğrulamak için yeni türler yaratmak.
Jacob Zimmerman

1
@Keyhan bu Python'da klasik bir yaklaşım, onlarca argümanlı fonksiyonlar için bile çok iyi çalışıyor. Buradaki hile isimlendirilmiş argümanları kullanmaktır (Java'da mevcut değildir!)
Voddan

1
Evet, aynı zamanda kullanmaya değer bir çözümdür, oluşturucu sınıfın bazı belirgin avantajları olan java'nın aksine, Kotlin'de çok açık değildir, C # geliştiricileriyle konuştu, C # da kotlin benzeri özelliklere sahip (varsayılan değer ve ne zaman params adlandırabilirsiniz) çağırıcı oluşturucu) da oluşturucu desenini kullanmıyordu.
Keyhan

1
@ vxh.viet bu tür davaların çoğu kotlinlang.org/docs/reference/… ile @JvmOverloads çözülebilir
voddan

4

Yapımcı olarak ekstra eğlenceler ilan eden birçok örnek gördüm. Ben şahsen bu yaklaşımı seviyorum. İnşaatçılar yazmak için çabadan tasarruf edin.

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

    }
}

İstisnalar atmak yerine hataları göstermek gibi bazı alanları DSL'de başlatılmaya zorlayabilecek bir yol bulamadım. Kimse biliyorsa bana bildirin.


2

Basit bir sınıf için ayrı bir kurucuya ihtiyacınız yoktur. Kirill Rakhman'ın açıkladığı gibi isteğe bağlı yapıcı argümanlarından yararlanabilirsiniz.

Daha karmaşık bir sınıfınız varsa Kotlin, Groovy tarzı Builders / DSL oluşturmanın bir yolunu sunar:

Güvenli Tip Oluşturucular

İşte bir örnek:

Github Örneği - Yapıcı / Birleştirici


Teşekkürler, ama Java'dan da kullanmayı düşünüyordum. Bildiğim kadarıyla isteğe bağlı argümanlar java'dan işe yaramayacaktı.
Keyhan


1

Partiye geç kaldım. Projede Builder desenini kullanmak zorunda kalsaydım aynı ikilemle de karşılaştım. Daha sonra, araştırmadan sonra Kotlin'in adlandırılmış argümanları ve varsayılan argümanları sağladığından kesinlikle gereksiz olduğunu fark ettim.

Gerçekten uygulamanız gerekiyorsa, Kirill Rakhman'ın cevabı en etkili şekilde nasıl uygulanacağına dair sağlam bir cevaptır. Yararlı bulabileceğiniz başka bir şey , uygulamalarında Java ve Kotlin ile karşılaştırabileceğiniz ve tezat oluşturabileceğiniz https://www.baeldung.com/kotlin-builder-pattern.


0

Desen ve uygulamanın Kotlin'de hemen hemen aynı kaldığını söyleyebilirim. Varsayılan değerler sayesinde bazen atlayabilirsiniz, ancak daha karmaşık nesne oluşturmak için oluşturucular hala atlanamayan kullanışlı bir araçtır.


Varsayılan değerlere sahip kuruculara göre, başlatıcı bloklarını kullanarak giriş doğrulaması yapabilirsiniz . Ancak, durum bilgisi olan bir şeye ihtiyacınız varsa (önden her şeyi belirtmek zorunda kalmamanız için), o zaman oluşturucu deseni hala yoludur.
mfulton26

Bana kodla ilgili basit bir örnek verebilir misiniz? E-posta doğrulaması ile ad ve e-posta alanına sahip basit bir Kullanıcı sınıfı söyleyin.
Keyhan

0

kotlin örneğinde isteğe bağlı parametre kullanabilirsiniz:

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

sonra

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

0
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

Kotlin'de aşağıdaki kodla temel bir Builder kalıbı uyguladım:

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

Ve sonunda

Java:

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

Kotlin:

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

0

Java istemcileri tarafından tüketilen bir API'yi (Kotlin dil yapılarından yararlanamayan) ortaya çıkaran bir Kotlin projesi üzerinde çalışıyordum. Bir @Builder ek açıklama yarattı yüzden onları Java kullanılabilir hale getirmek inşaatçılar eklemek zorunda: https://github.com/ThinkingLogic/kotlin-builder-annotation - temelde KOTLIN için Lombok @Builder açıklama için bir yedek var.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.