कोटलिन में बिल्डर पैटर्न कैसे लागू करें?


146

हाय मैं कोटलिन दुनिया में नौसिखिया हूं। मुझे वह पसंद है जो मैंने अब तक देखा है और हमारे कुछ पुस्तकालयों का उपयोग करने के लिए सोचना शुरू कर दिया है जिनका उपयोग हम जावा से कोटलिन में करते हैं।

ये पुस्तकालय बसने वाले, गेटर्स और बिल्डर कक्षाओं के साथ पूजो से भरे हुए हैं। अब मुझे पता चला है कि कोटलिन में बिल्डर्स को लागू करने का सबसे अच्छा तरीका क्या है, लेकिन कोई सफलता नहीं है।

दूसरा अद्यतन: सवाल यह है कि कोटलिन में कुछ मापदंडों के साथ एक साधारण पूजो के लिए बिल्डर डिजाइन-पैटर्न कैसे लिखें? नीचे दिया गया कोड जावा कोड लिखकर और फिर कोटलिन में बदलने के लिए एक्लिप्स-कोटलिन-प्लगइन का उपयोग करके मेरा प्रयास है।

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
क्या आपको जरूरत है modelऔर yearआपस में मिलनसार होने की? क्या आप एक Carनिर्माण के बाद उन्हें बदलते हैं ?
वोड्डन

मुझे लगता है कि उन्हें अपरिवर्तनीय हाँ होना चाहिए। इसके अलावा आप यह सुनिश्चित करना चाहते हैं कि वे दोनों सेट हैं और खाली नहीं हैं
कीहन

1
आप बिल्डर वर्ग को अपने आप उत्पन्न करने के लिए इस github.com/jffiorillo/jvmbuilder एनोटेशन प्रोसेसर का भी उपयोग कर सकते हैं।
जोसेफ

@ जोसफ अच्छा विचार है कि इसे मानक कोटलिन में जोड़ें। यह कोटलिन में लिखे गए पुस्तकालयों के लिए उपयोगी है।
कीहान

जवाबों:


273

और सबसे पहले, ज्यादातर मामलों में आपको कोटलिन में बिल्डरों का उपयोग करने की आवश्यकता नहीं है क्योंकि हमारे पास डिफ़ॉल्ट और नामित तर्क हैं। यह आपको लिखने में सक्षम बनाता है

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

और इसे इस तरह उपयोग करें:

val car = Car(model = "X")

यदि आप बिल्‍डर का उपयोग करना चाहते हैं, तो यहां बताया गया है कि आप इसे कैसे कर सकते हैं:

बिल्डर बनाना कोई companion objectमतलब नहीं है क्योंकि objects सिंगललेट हैं। इसके बजाय इसे नेस्टेड क्लास (जो कि कोटलिन में डिफ़ॉल्ट रूप से स्थिर है) घोषित करें।

कंस्ट्रक्टर के गुणों को स्थानांतरित करें ताकि ऑब्जेक्ट को नियमित रूप से तुरंत बनाया जा सके (यदि यह नहीं होना चाहिए तो कंस्ट्रक्टर को निजी बनाएं) और एक माध्यमिक कंस्ट्रक्टर का उपयोग करें जो एक बिल्डर को लेता है और प्राथमिक कंस्ट्रक्टर को सौंपता है। कोड निम्नानुसार दिखेगा:

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
कुछ भी नहीं, लेकिन प्रश्न के लेखक ने विशेष रूप से पूछा कि बिल्डर पैटर्न को कैसे लागू किया जाए।
किरील रहमान

4
मुझे अपने आप को सही करना चाहिए, बिल्डर पैटर्न के कुछ फायदे हैं, जैसे आप आंशिक रूप से निर्मित बिल्डर को किसी अन्य विधि से पारित कर सकते हैं। लेकिन आप सही हैं, मैं एक टिप्पणी जोड़ूंगा।
किरिल रहमान

3
@KirillRakhman जावा से बिल्डर को कॉल करने के बारे में कैसे? क्या बिल्डर को जावा उपलब्ध कराने का एक आसान तरीका है?
कीहन

6
सभी तीन संस्करणों को जावा से बुलाया जा सकता है जैसे Car.Builder builder = new Car.Builder();:। हालाँकि केवल पहले संस्करण में एक धाराप्रवाह इंटरफ़ेस है, इसलिए दूसरे और तीसरे संस्करण के लिए कॉल को जंजीर नहीं बनाया जा सकता है।
किरिल रहमान

10
मुझे लगता है कि शीर्ष पर कोटलिन का उदाहरण केवल एक संभावित उपयोग के मामले की व्याख्या करता है। मैं बिल्डरों का उपयोग करने का मुख्य कारण एक उत्परिवर्तनीय वस्तु को एक अपरिवर्तनीय में परिवर्तित करना है। यही है, मुझे "निर्माण" करते समय इसे समय के साथ बदलना होगा और फिर एक अपरिवर्तनीय वस्तु के साथ आना होगा। कम से कम मेरे कोड में कोड के केवल एक या 2 उदाहरण हैं जिनमें मापदंडों के इतने भिन्नता हैं कि मैं कई अलग-अलग बिल्डरों के बजाय एक बिल्डर का उपयोग करूंगा। लेकिन एक अपरिवर्तनीय वस्तु बनाने के लिए, मेरे पास कुछ मामले हैं जहां एक बिल्डर निश्चित रूप से सबसे साफ तरीका है जिसके बारे में मैं सोच सकता हूं।
यम ०

21

एक दृष्टिकोण निम्नलिखित की तरह कुछ करना है:

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

आपका बहुत बहुत धन्यवाद! आपने मेरा दिन बना दिया! आपके उत्तर को समाधान के रूप में चिह्नित किया जाना चाहिए।
sVd

9

क्योंकि मैं JSON से ऑब्जेक्ट्स पार्स करने के लिए जैक्सन लाइब्रेरी का उपयोग कर रहा हूं, मुझे एक खाली कंस्ट्रक्टर की आवश्यकता है और मेरे पास वैकल्पिक फ़ील्ड नहीं हो सकते। साथ ही सभी फील्ड्स को परस्पर बदलना होगा। फिर मैं इस अच्छे वाक्यविन्यास का उपयोग कर सकता हूं जो बिल्डर पैटर्न के समान काम करता है:

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

8
जैक्सन में आपको वास्तव में एक खाली कंस्ट्रक्टर की आवश्यकता नहीं होती है, और खेतों को पारस्परिक होने की आवश्यकता नहीं होती है। आपको बस अपने कंस्ट्रक्टर मापदंडों को एनोटेट करना होगा@JsonProperty
बास्टियन वोइगेट

2
@JsonPropertyयदि आप -parametersस्विच के साथ संकलन करते हैं, तो आपको अब और एनोटेट करने की आवश्यकता नहीं है ।
अमीर अबीरी

2
जैक्सन वास्तव में एक बिल्डर का उपयोग करने के लिए कॉन्फ़िगर किया जा सकता है।
कीहन

1
यदि आप जैकसन-मॉड्यूल-कोटलिन मॉड्यूल को अपनी परियोजना में जोड़ते हैं, तो आप बस डेटा वर्गों का उपयोग कर सकते हैं और यह काम करेगा।
निल्स ब्रुनेसे

2
यह बिल्डर पैटर्न के समान कैसे काम कर रहा है? आप अंतिम उत्पाद को त्वरित कर रहे हैं और फिर स्वैपिंग / जानकारी जोड़ रहे हैं। बिल्डर पैटर्न का पूरा बिंदु अंतिम उत्पाद प्राप्त करने में सक्षम नहीं है जब तक कि सभी आवश्यक जानकारी मौजूद न हो। .Apply () हटाना आपको एक अपरिभाषित कार के साथ छोड़ देता है। बिल्डर से सभी कंस्ट्रक्टर तर्क को हटाकर आपको एक कार बिल्डर के साथ छोड़ दिया जाता है, और यदि आप इसे कार में बनाने की कोशिश करते हैं, तो संभवतः आप मॉडल और वर्ष को निर्दिष्ट नहीं करने के लिए एक अपवाद में भाग लेंगे। ये एक ही चीज नहीं हैं।
जीरोस्टैटिक

7

मैंने व्यक्तिगत रूप से कोटलिन में एक बिल्डर को कभी नहीं देखा है, लेकिन शायद यह सिर्फ मुझे है।

initब्लॉक में होने वाली सभी सत्यापन की आवश्यकता है :

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

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

यहाँ मैंने यह अनुमान लगाने के लिए एक स्वतंत्रता ली कि आप वास्तव में नहीं चाहते modelऔर yearपरिवर्तनशील होना चाहिए। इसके अलावा उन डिफ़ॉल्ट मूल्यों का कोई मतलब नहीं है, (विशेषकर के nullलिए name) लेकिन मैंने प्रदर्शन उद्देश्यों के लिए एक छोड़ दिया।

एक राय: जावा नामांकित मापदंडों के बिना रहने के लिए एक मतलब के रूप में उपयोग किए जाने वाले बिल्डर पैटर्न। नामित मापदंडों (जैसे कोटलिन या पायथन) वाली भाषाओं में, रचनाकारों की लंबी सूचियों (शायद वैकल्पिक) मापदंडों के साथ एक अच्छा अभ्यास है।


2
उत्तर के लिए बहुत बहुत धन्यवाद। मुझे आपका दृष्टिकोण पसंद है लेकिन नकारात्मक पक्ष एक वर्ग के लिए है जिसमें कई पैरामीटर हैं जो कंस्ट्रक्टर का उपयोग करने के लिए इतना अनुकूल नहीं है और वर्ग का परीक्षण भी करता है।
कीहन

1
+ कीहनन दो अन्य तरीके से आप सत्यापन कर सकते हैं, यह मानते हुए कि सत्यापन खेतों के बीच नहीं होता है: 1) संपत्ति प्रतिनिधियों का उपयोग करें जहां सेटर सत्यापन करता है - यह सामान्य सेटर होने के समान ही बहुत कुछ है जो सत्यापन 2 से बचता है। आदिम जुनून और खुद को मान्य करने के लिए नए प्रकार बनाएं।
याकूब ज़िमरमैन

1
@ कायण यह पायथन में एक क्लासिक दृष्टिकोण है, यह दसियों तर्कों वाले कार्यों के लिए भी बहुत अच्छा काम करता है। यहाँ ट्रिक को नामित तर्कों का उपयोग करना है (जावा में उपलब्ध नहीं है!)
वोड्डन

1
हां, यह भी उपयोग करने लायक एक समाधान है, यह जावा के विपरीत लगता है जहां बिल्डर वर्ग के कुछ स्पष्ट फायदे हैं, कोटलिन में यह स्पष्ट नहीं है, सी # डेवलपर्स से बात की, सी # में भी कोटलिन जैसी विशेषताएं हैं (डिफ़ॉल्ट मान और आप पार्सल का नाम दे सकते हैं कॉलिंग कंस्ट्रक्टर) उन्होंने बिल्डर पैटर्न का उपयोग नहीं किया।
कीहन

1
@ vxh.viet ऐसे कई मामलों को @JvmOverloads kotlinlang.org/docs/reference/… के
voddan

4

मैंने कई उदाहरणों को देखा है जो बिल्डरों के रूप में अतिरिक्त फन की घोषणा करते हैं। मुझे व्यक्तिगत रूप से यह दृष्टिकोण पसंद है। बिल्डरों को लिखने का प्रयास बचाओ।

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

    }
}

मुझे अभी तक ऐसा कोई तरीका नहीं मिला है जो डीएसएल में कुछ फ़ील्ड्स को इनिशियलाइज़ करने के लिए बाध्य कर सके जैसे कि अपवादों को फेंकने के बजाय त्रुटियों को दिखाना। अगर कोई जानता है तो मुझे बताएं।


2

एक साधारण वर्ग के लिए आपको एक अलग बिल्डर की आवश्यकता नहीं है। आप वैकल्पिक निर्माण तर्कों का उपयोग कर सकते हैं जैसा कि किरिल रहमान ने वर्णित किया है।

यदि आपके पास अधिक जटिल वर्ग है तो कोटलिन ग्रोवी स्टाइल बिल्डर्स / डीएसएल बनाने का एक तरीका प्रदान करता है:

टाइप-सेफ बिल्डर्स

यहाँ एक उदाहरण है:

गिथब उदाहरण - बिल्डर / असेंबलर


धन्यवाद, लेकिन मैं इसे जावा से भी उपयोग करने के बारे में सोच रहा था। जहाँ तक मुझे पता है कि वैकल्पिक तर्क जावा से काम नहीं करेंगे।
कीहान

2

लोगों को अब कोटलिन के टाइप-सेफ बिल्डर्स की जांच करनी चाहिए ।

ऑब्जेक्ट निर्माण के उक्त तरीके के प्रयोग से कुछ इस तरह दिखेगा:

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

एक अच्छा 'इन-एक्शन' उपयोग उदाहरण वाडिन-ऑन-कोटलिन फ्रेमवर्क है, जो विचारों और घटकों को इकट्ठा करने के लिए टाइपफोर बिल्डरों का उपयोग करता है ।


1

मुझे पार्टी की देर है। मुझे भी उसी दुविधा का सामना करना पड़ा अगर मुझे प्रोजेक्ट में बिल्डर पैटर्न का उपयोग करना पड़ा। बाद में, अनुसंधान के बाद मुझे एहसास हुआ कि यह बिल्कुल अनावश्यक है क्योंकि कोटलिन पहले से ही नामित तर्क और डिफ़ॉल्ट तर्क प्रदान करता है।

यदि आपको वास्तव में लागू करने की आवश्यकता है, तो किरिल रहमान का जवाब इस बात का ठोस जवाब है कि सबसे प्रभावी तरीके से कैसे लागू किया जाए। एक और चीज जो आपको उपयोगी लग सकती है वह है https://www.baeldung.com/kotlin-builder-pattern जिसकी आप तुलना कर सकते हैं और इसके कार्यान्वयन पर जावा और कोटलीन के साथ विपरीत कर सकते हैं।


0

मैं कहूंगा कि पैटर्न और कार्यान्वयन कोटलिन में बहुत समान है। आप कभी-कभी इसे डिफ़ॉल्ट मानों की बदौलत छोड़ सकते हैं, लेकिन अधिक जटिल वस्तु निर्माण के लिए, बिल्डर्स अभी भी एक उपयोगी उपकरण हैं जिसे छोड़ा नहीं जा सकता है।


जहां तक ​​डिफॉल्ट मान वाले कंस्ट्रक्टरों का है, तो आप इनिशलाइज़र ब्लॉक का उपयोग करके इनपुट का सत्यापन भी कर सकते हैं । हालाँकि, अगर आपको कुछ स्टेटफुल चाहिए (ताकि आपको आगे सब कुछ निर्दिष्ट न करना पड़े) तो बिल्डर पैटर्न अभी भी जाने का रास्ता है।
mfulton26

क्या आप मुझे कोड के साथ एक सरल उदाहरण दे सकते हैं? ईमेल के लिए सत्यापन के साथ नाम और ईमेल क्षेत्र के साथ एक साधारण उपयोगकर्ता वर्ग कहें।
कीहन

0

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

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

मैंने कोड कोड के साथ कोटलिन में एक बुनियादी बिल्डर पैटर्न लागू किया:

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

Kotlin:

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

0

मैं एक कोटलिन परियोजना पर काम कर रहा था जिसने जावा क्लाइंट द्वारा उपभोग किए गए एक एपीआई को उजागर किया (जो कोटलिन भाषा के निर्माण का लाभ नहीं उठा सकता है)। हमें जावा में उन्हें उपयोग करने योग्य बनाने के लिए बिल्डरों को जोड़ना था, इसलिए मैंने एक @Builder एनोटेशन बनाया: https://github.com/ThinkingLogic/kotlin-builder-annotation - यह मूल रूप से कोटलिन के लिए लोम्बोक -Builder एनोटेशन के लिए एक प्रतिस्थापन है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.