Menu
Coddy logo textTech

Patron Factory

Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 43 sur 57.

Le modèle factory centralise la décision concernant la classe à créer. L’appelant décrit ce dont il a besoin et reçoit le type de l’interface, sans nommer de classe concrète :

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

Dans main :

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

Sortie :

sms to ada
null

En Kotlin, une fabrique est souvent une fonction dans l’objet compagnon de l’interface ou de la classe de base, de sorte que les appelants écrivent Shape.from("circle 2"). Elle peut analyser et vérifier l’entrée avant de créer quoi que ce soit :

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
}

Dans main :

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

Résultat :

9.0
null

Les appelants ne voient que l’interface, donc la fabrique peut modifier la classe qu’elle renvoie sans les toucher. Ici, les petites données restent en mémoire et les grandes données sont écrites sur le disque :

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

À l’intérieur de main :

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

Sortie :

memory
disk at /tmp/cache

Une fabrique peut également être une valeur : une fonction de type (String) -> Animal crée des objets sans que l’appelant connaisse leur classe. Une map associant des noms à de telles fonctions est un registre, et une référence de constructeur telle que ::Dog est une fonction fabrique prête à l’emploi :

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

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

Sortie :

Tom says Meow
challenge icon

Défi

Facile

Plusieurs exportateurs implémentent l’interface fournie Exporter (extension et export(title, lines)). Écrivez MarkdownExporter (extension md : une ligne # Title et une ligne - item par ligne), CsvExporter (extension csv : title,Title, puis line,item pour chaque ligne) et PlainExporter (extension txt : le titre en lettres majuscules, puis les lignes inchangées). Écrivez ensuite l’objet fabrique ExporterFactory : create(format) renvoie l’exportateur correspondant à markdown, csv ou plain, sans tenir compte de la casse, ou null, et supported() renvoie les noms des formats.

Le code fourni lit un format, un titre et les lignes, puis affiche report.md et le texte exporté, ou unsupported format pdf; try markdown, csv, plain.

Votre code doit se trouver dans MarkdownExporter.kt, CsvExporter.kt, PlainExporter.kt, ExporterFactory.kt et Exporter.kt. Main.kt contient le code fourni pour les entrées et sorties et ne peut pas être modifié.

Essayez vous-même

fun main() {
    // Code d'entrée/sortie fourni : le garder tel quel
    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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Playground Kotlin