Menu
Coddy logo textTech

תבנית 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
challenge icon

אתגר

קל

כמה מייצאים מממשים את הממשק שסופק 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)))
    }
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: Kotlin אונליין