Menu
Coddy logo textTech

Pattern Factory

Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 43 di 57.

Il pattern factory concentra in un unico punto la decisione su quale classe creare. Il chiamante descrive ciò di cui ha bisogno e riceve il tipo di interfaccia, senza indicare una classe concreta:

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

All'interno di main:

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

Output:

sms to ada
null

In Kotlin una factory è spesso una funzione nell'oggetto companion dell'interfaccia o della classe base, quindi chi chiama scrive Shape.from("circle 2"). Può analizzare e controllare l'input prima di creare qualsiasi cosa:

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
}

Dentro main:

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

Output:

9.0
null

I chiamanti vedono solo l’interfaccia, quindi la factory può cambiare la classe che restituisce senza modificarli. Qui i dati piccoli restano in memoria e quelli grandi vengono salvati su disco:

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

All’interno di main:

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

Output:

memory
disk at /tmp/cache

Una factory può anche essere un valore: una funzione di tipo (String) -> Animal crea oggetti senza che chi la chiama conosca la loro classe. Una mappa dai nomi a tali funzioni è un registro, e un riferimento a un costruttore come ::Dog è una funzione factory già pronta:

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

All'interno di 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()}")

Output:

Tom says Meow
challenge icon

Sfida

Facile

Diversi esportatori implementano l’interfaccia fornita Exporter (extension e export(title, lines)). Scrivi MarkdownExporter (estensione md: una riga # Title e una riga - item per ogni riga), CsvExporter (estensione csv: title,Title e poi line,item per ogni riga) e PlainExporter (estensione txt: il titolo in lettere maiuscole, poi le righe invariate). Scrivi quindi l’oggetto factory ExporterFactory: create(format) restituisce l’esportatore per markdown, csv o plain, ignorando le maiuscole e le minuscole, oppure null, mentre supported() restituisce i nomi dei formati.

Il codice fornito legge un formato, un titolo e le righe, e stampa report.md e il testo esportato, oppure unsupported format pdf; try markdown, csv, plain.

Il tuo codice va inserito in MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt e Exporter.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

fun main() {
    // Supplied input/output code: keep it as it is
    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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Playground Kotlin