תבנית Factory
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Kotlin של Coddy. שיעור 43 מתוך 57.
תבנית הfactory מרכזת במקום אחד את ההחלטה איזו מחלקה ליצור. הקוד הקורא מתאר מה הוא צריך ומקבל את סוג הממשק, בלי לציין מחלקה קונקרטית:
interface Notifier {
fun send(to: String): String
}
class EmailNotifier : Notifier {
override fun send(to: String) = "email to $to"
}
class SmsNotifier : Notifier {
override fun send(to: String) = "sms to $to"
}
object NotifierFactory {
fun create(channel: String): Notifier? = when (channel) {
"email" -> EmailNotifier()
"sms" -> SmsNotifier()
else -> null
}
}בתוך main:
println(NotifierFactory.create("sms")?.send("ada"))
println(NotifierFactory.create("fax")?.send("bo"))פלט:
sms to ada
nullב-Kotlin, factory היא לעיתים קרובות פונקציה באובייקט הנלווה של הממשק או מחלקת הבסיס, ולכן הקוראים כותבים Shape.from("circle 2"). היא יכולה לנתח ולבדוק את הקלט לפני שהיא יוצרת משהו:
interface Shape {
fun area(): Double
companion object {
fun from(text: String): Shape? {
val p = text.split(" ")
val n = p.getOrNull(1)?.toDoubleOrNull() ?: return null
return when (p[0]) {
"circle" -> Circle(n)
"square" -> Square(n)
else -> null
}
}
}
}
class Circle(val r: Double) : Shape {
override fun area() = 3.14 * r * r
}
class Square(val s: Double) : Shape {
override fun area() = s * s
}בתוך main:
println(Shape.from("square 3")?.area())
println(Shape.from("circle x")?.area())פלט:
9.0
nullהלקוחות רואים רק את הממשק, ולכן המפעל יכול לשנות איזו מחלקה הוא מחזיר בלי לגעת בהם. כאן נתונים קטנים נשארים בזיכרון, ונתונים גדולים נשמרים בדיסק:
interface Storage {
fun describe(): String
}
class MemoryStorage : Storage {
override fun describe() = "memory"
}
class DiskStorage(val path: String) : Storage {
override fun describe() = "disk at $path"
}
fun storageFor(sizeMb: Int): Storage = if (sizeMb < 100) MemoryStorage() else DiskStorage("/tmp/cache")בתוך main:
println(storageFor(5).describe())
println(storageFor(500).describe())פלט:
memory
disk at /tmp/cacheמפעל יכול להיות גם ערך: פונקציה מהטיפוס (String) -> Animal יוצרת אובייקטים בלי שהקורא יידע מה המחלקה שלהם. מיפוי משמות לפונקציות כאלה הוא רישום, והפניה לבנאי כמו ::Dog היא פונקציית מפעל מוכנה לשימוש:
open class Animal(val name: String) {
open fun sound() = "..."
}
class Dog(name: String) : Animal(name) {
override fun sound() = "Woof"
}
class Cat(name: String) : Animal(name) {
override fun sound() = "Meow"
}בתוך main:
val makers: Map<String, (String) -> Animal> = mapOf("dog" to ::Dog, "cat" to ::Cat)
val pet = makers.getValue("cat")("Tom")
println("${pet.name} says ${pet.sound()}")פלט:
Tom says Meowאתגר
קלכמה מייצאים מממשים את הממשק שסופק Exporter (extension ו־export(title, lines)). כתבו את MarkdownExporter (סיומת md: שורת # Title ושורת - item אחת לכל שורה), את CsvExporter (סיומת csv: title,Title ולאחר מכן line,item לכל שורה), ואת PlainExporter (סיומת txt: הכותרת באותיות גדולות, ולאחריה השורות ללא שינוי). לאחר מכן כתבו את אובייקט המפעל ExporterFactory: הפונקציה create(format) מחזירה את המייצא עבור markdown, csv או plain, ללא תלות באותיות גדולות או קטנות, או null, והפונקציה supported() מחזירה את שמות הפורמטים.
הקוד שסופק קורא פורמט, כותרת ושורות, ומדפיס את report.md ואת הטקסט שיוצא, או את unsupported format pdf; try markdown, csv, plain.
הקוד שלכם ייכתב בקבצים MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt ו־Exporter.kt. הקובץ Main.kt מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.
נסו בעצמכם
fun main() {
// קוד הקלט/פלט שסופק: השאירו אותו כפי שהוא
val input = generateSequence(::readLine).toList()
val exporter = ExporterFactory.create(input[0])
if (exporter == null) {
println("unsupported format ${input[0]}; try ${ExporterFactory.supported().joinToString(", ")}")
} else {
println("report.${exporter.extension}")
println(exporter.export(input[1], input.drop(2)))
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1מחלקות ואובייקטים
עבודה עם קבציםהגדרת מחלקותבנאים ראשייםבלוקי init ואימות נתוניםבנאים משנייםסיכום – כרטיס ספרייהתרגלו בעצמכם: Kotlin אונליין