Menu
Coddy logo textTech

Fabrikmuster

Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 43 von 57.

Das Fabrikmuster legt die Entscheidung darüber, welche Klasse erstellt werden soll, an einer Stelle fest. Der Aufrufer beschreibt, was er benötigt, und erhält den Schnittstellentyp, ohne eine konkrete Klasse zu benennen:

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

Innerhalb von main:

println(NotifierFactory.create("sms")?.send("ada"))
println(NotifierFactory.create("fax")?.send("bo"))

Ausgabe:

sms to ada
null

In Kotlin ist eine Factory oft eine Funktion im companion object des Interfaces oder der Basisklasse, sodass Aufrufer Shape.from("circle 2") schreiben. Sie kann die Eingabe analysieren und überprüfen, bevor sie etwas erstellt:

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
}

Innerhalb von main:

println(Shape.from("square 3")?.area())
println(Shape.from("circle x")?.area())

Ausgabe:

9.0
null

Aufrufer sehen nur die Schnittstelle, daher kann die Factory ändern, welche Klasse sie zurückgibt, ohne sie anzupassen. Hier bleiben kleine Daten im Speicher, und große Daten werden auf die Festplatte geschrieben:

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

Innerhalb von main:

println(storageFor(5).describe())
println(storageFor(500).describe())

Ausgabe:

memory
disk at /tmp/cache

Eine Factory kann auch ein Wert sein: Eine Funktion vom Typ (String) -> Animal erstellt Objekte, ohne dass der Aufrufer ihre Klasse kennen muss. Eine Zuordnung von Namen zu solchen Funktionen ist eine Registry, und eine Konstruktorreferenz wie ::Dog ist eine fertige Factory-Funktion:

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

In 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()}")

Ausgabe:

Tom says Meow
challenge icon

Aufgabe

Einfach

Mehrere Exporter implementieren die bereitgestellte Schnittstelle Exporter (extension und export(title, lines)). Schreibe MarkdownExporter (Erweiterung md: eine # Title-Zeile und eine - item-Zeile pro Zeile), CsvExporter (Erweiterung csv: title,Title und anschließend line,item pro Zeile) und PlainExporter (Erweiterung txt: den Titel in Großbuchstaben, anschließend die Zeilen unverändert). Schreibe dann das Factory-Objekt ExporterFactory: create(format) gibt den Exporter für markdown, csv oder plain zurück, wobei die Groß- und Kleinschreibung ignoriert wird, oder null; supported() gibt die Formatnamen zurück.

Der bereitgestellte Code liest ein Format, einen Titel und die Zeilen ein und gibt report.md sowie den exportierten Text aus oder unsupported format pdf; try markdown, csv, plain.

Dein Code gehört in MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt und Exporter.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

fun main() {
    // Gelieferter Ein-/Ausgabe-Code: belasse ihn wie er ist
    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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Kotlin-Playground