Menu
Coddy logo textTech

Patrón Factory

Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 43 de 57.

El patrón factory concentra en un solo lugar la decisión sobre qué clase crear. La persona que llama describe lo que necesita y recibe el tipo de interfaz, sin nombrar una clase 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
    }
}

Dentro de main:

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

Salida:

sms to ada
null

En Kotlin, una fábrica suele ser una función en el objeto complementario de la interfaz o de la clase base, por lo que quienes llaman escriben Shape.from("circle 2"). Puede analizar y comprobar la entrada antes de crear nada:

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 de main:

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

Salida:

9.0
null

Los llamadores solo ven la interfaz, por lo que la fábrica puede cambiar la clase que devuelve sin modificarlos. Aquí, los datos pequeños permanecen en la memoria y los datos grandes se guardan en el 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")

Dentro de main:

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

Salida:

memory
disk at /tmp/cache

Una fábrica también puede ser un valor: una función del tipo (String) -> Animal crea objetos sin que quien la llama conozca su clase. Un mapa de nombres a dichas funciones es un registro, y una referencia a un constructor como ::Dog es una función de fábrica lista para usar:

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

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

Salida:

Tom says Meow
challenge icon

Desafío

Fácil

Varios exportadores implementan la interfaz proporcionada Exporter (extension y export(title, lines)). Escribe MarkdownExporter (extensión md: una línea # Title y una línea - item por cada línea), CsvExporter (extensión csv: title,Title y después line,item por cada línea) y PlainExporter (extensión txt: el título en letras mayúsculas y después las líneas sin cambios). Después escribe el objeto fábrica ExporterFactory: create(format) devuelve el exportador para markdown, csv o plain, ignorando las mayúsculas y minúsculas, o null, y supported() devuelve los nombres de los formatos.

El código proporcionado lee un formato, un título y las líneas, e imprime report.md y el texto exportado, o unsupported format pdf; try markdown, csv, plain.

Tu código va en MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt y Exporter.kt. Main.kt contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

fun main() {
    // Código de entrada/salida suministrado: déjalo tal como está
    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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Playground de Kotlin