Menu
CoddyTech

Pola Builder

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan Kotlin di Coddy. Pelajaran 48 dari 57.

Pola builder membuat object secara bertahap dan memeriksa seluruhnya pada bagian akhir. Builder mengumpulkan bagian-bagiannya melalui method berantai yang mengembalikan builder itu sendiri, lalu build() membuat object yang sudah selesai:

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

Di dalam main:

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

Output:

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

apply mempersingkat setiap langkah: fungsi ini menjalankan blok dengan object sebagai this dan mengembalikan object tersebut, sehingga satu langkah menjadi satu baris. check(condition) { message } melempar IllegalStateException, padanan require untuk keadaan object:

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

Di dalam main:

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

Output:

POST /users [Accept: json]

Fungsi yang menerima lambda dengan receiver, bertipe Builder.() -> Unit, mengubah builder menjadi DSL kecil (bahasa khusus domain): di dalam kurung kurawal, builder menjadi this, jadi pemanggil hanya menulis konfigurasi:

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

Di dalam main:

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

Output:

== Lunch ==
soup 4.50
salad 6.00

Argumen bernama dan default Kotlin sudah mencakup kasus sederhana: Pizza(size = 30, cheese = true) memberi nama pada setiap pilihan saat pemanggilan. Builder berguna saat object dirakit dalam beberapa langkah, mengumpulkan list, atau memerlukan pemeriksaan antarbidang sebelum dapat dibuat:

data class Pizza(val size: Int = 26, val cheese: Boolean = true, val olives: Boolean = false)
val simple = Pizza(size = 30, olives = true)        // tidak perlu builder

val mail = EmailBuilder()                            // builder untuk menyusun secara bertahap
    .to("ada@x.io")
    .to("bo@x.io")                                  // mengumpulkan nilai dalam list
    .subject("Plan")
    .build()                                        // memeriksa semuanya di akhir
challenge icon

Tantangan

Mudah

Tulis builder untuk data class Email yang disediakan. EmailBuilder memiliki langkah-langkah berantai: to(address) (dapat dipanggil beberapa kali dan menambahkan penerima), subject(text), body(text), dan urgent(). build() mengembalikan Email atau melempar IllegalStateException dengan no recipients atau no subject (sesuai urutan pemeriksaan tersebut). Email yang mendesak akan mendapat [URGENT] di depan subject-nya.

Kode yang disediakan membaca perintah (to ada@x.io, subject Hello, body text, urgent, send), memulai builder baru setelah setiap baris ---, lalu mencetak email yang dibuat, To: ada@x.io, bo@x.io | [URGENT] Hello | text, atau error: no subject.

Kodemu ditulis di EmailBuilder.kt dan Email.kt. Main.kt berisi kode input/output yang disediakan dan tidak boleh diedit.

Coba sendiri

fun main() {
    // Kode input/output yang disediakan: biarkan tetap seperti ini
    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 iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Playground Kotlin