Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط الباني

جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 48 من 57.

ينشئ نمط الباني كائنًا خطوةً بخطوة ويتحقق من الأمر بأكمله في النهاية. يجمع الباني الأجزاء في أساليب قابلة للتسلسل تُرجع الباني نفسه، وينشئ build() الكائن المكتمل:

data class Email(val to: List<String>, val subject: String)

class EmailBuilder {
    private val recipients = mutableListOf<String>()
    private var subject = ""

    fun to(address: String): EmailBuilder {
        recipients.add(address)
        return this
    }
    fun subject(text: String): EmailBuilder {
        subject = text
        return this
    }
    fun build(): Email {
        check(recipients.isNotEmpty()) { "no recipients" }
        return Email(recipients.toList(), subject)
    }
}

داخل main:

val mail = EmailBuilder().to("ada@x.io").to("bo@x.io").subject("Lunch").build()
println(mail)

الناتج:

Email(to=[ada@x.io, bo@x.io], subject=Lunch)

تختصر apply كل خطوة: فهي تشغّل كتلة مع الكائن بوصفه this وتُعيد الكائن، لذا تصبح الخطوة سطرًا واحدًا. ترمي check(condition) { message } استثناءً من نوع IllegalStateException، وهو النظير لـ require لحالة الكائن:

class RequestBuilder {
    var url = ""
    var method = "GET"
    private val headers = mutableListOf<String>()

    fun header(name: String, value: String) = apply { headers.add("$name: $value") }

    fun build(): String {
        check(url.isNotEmpty()) { "no url" }
        return "$method $url " + headers
    }
}

داخل main:

val request = RequestBuilder().apply {
    url = "/users"
    method = "POST"
    header("Accept", "json")
}.build()
println(request)

الناتج:

POST /users [Accept: json]

إن الدالة التي تأخذ لامبدا مع مُستقبِل، من النوع Builder.() -> Unit، تحوّل المُنشئ إلى DSL صغيرة (لغة خاصة بالمجال): داخل الأقواس المعقوفة، يكون المُنشئ هو this، لذا لا يكتب المستدعي سوى الإعدادات:

class MenuBuilder(private val title: String) {
    private val items = mutableListOf<String>()

    fun item(name: String, cents: Int) {
        items.add("$name %d.%02d".format(cents / 100, cents % 100))
    }

    fun build() = "== $title ==\n" + items.joinToString("\n")
}

fun menu(title: String, block: MenuBuilder.() -> Unit): String = MenuBuilder(title).apply(block).build()

داخل main:

val lunch = menu("Lunch") {
    item("soup", 450)
    item("salad", 600)
}
println(lunch)

الناتج:

== Lunch ==
soup 4.50
salad 6.00

تغطي الوسائط المُسمّاة والافتراضية في Kotlin الحالات البسيطة بالفعل: يحدّد Pizza(size = 30, cheese = true) كل اختيار في موضع الاستدعاء. يكون المُنشئ مفيدًا عندما يُجمَّع الكائن عبر عدة خطوات، أو يجمع قوائم، أو يحتاج إلى إجراء عمليات تحقق عبر الحقول قبل أن يصبح موجودًا:

data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true)        // لا حاجة إلى builder

val mail = EmailBuilder()                            // builder للتجميع خطوة بخطوة
    .to("ada@x.io")
    .to("bo@x.io")                                  // يجمع قائمة
    .subject("Plan")
    .build()                                        // يتحقق من كل شيء في النهاية
challenge icon

التحدي

سهل

اكتب بنّاءً لفئة البيانات المعطاة Email. يحتوي EmailBuilder على خطوات قابلة للتسلسل: to(address) (يمكن استدعاؤها عدة مرات وتضيف مستلمين)، وsubject(text)، وbody(text)، وurgent(). تُرجع build() كائنًا من نوع Email أو ترمي IllegalStateException مع no recipients أو no subject (بهذا الترتيب من عمليات التحقق). يُضاف إلى مقدمة موضوع البريد الإلكتروني العاجل [URGENT] .

يقرأ الكود المعطى أوامر (to ada@x.io، وsubject Hello، وbody text، وurgent، وsend)، ويبدأ بنّاءً جديدًا بعد كل سطر ---، ويطبع البريد الإلكتروني المُنشأ، To: ada@x.io, bo@x.io | [URGENT] Hello | text، أو error: no subject.

ضع الكود الخاص بك في EmailBuilder.kt وEmail.kt. يحتوي Main.kt على كود الإدخال/الإخراج المعطى ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    var builder = EmailBuilder()
    for (cmd in input) {
        when {
            cmd == "send" -> {
                try {
                    println(builder.build())
                } catch (e: IllegalStateException) {
                    println("error: ${e.message}")
                }
            }
            cmd == "---" -> builder = EmailBuilder()
            cmd == "urgent" -> builder.urgent()
            cmd.startsWith("to ") -> builder.to(cmd.drop(3))
            cmd.startsWith("subject ") -> builder.subject(cmd.drop(8))
            else -> builder.body(cmd.drop(5))
        }
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Kotlin