תבנית Builder
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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) // אין צורך בבילדר
val mail = EmailBuilder() // בילדר להרכבה שלב אחר שלב
.to("ada@x.io")
.to("bo@x.io") // אוסף לרשימה
.subject("Plan")
.build() // בודק הכול בסוףאתגר
קלכתבו בונה עבור מחלקת הנתונים שסופקה 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))
}
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1מחלקות ואובייקטים
עבודה עם קבציםהגדרת מחלקותבנאים ראשייםבלוקי init ואימות נתוניםבנאים משנייםסיכום – כרטיס ספרייהתרגלו בעצמכם: Kotlin אונליין