Menu
Coddy logo textTech

Patron décorateur

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

Le motif décorateur ajoute un comportement à un objet en l’enveloppant dans un autre objet doté de la même interface. Le décorateur délègue à l’objet qu’il enveloppe et ajoute sa propre partie, ce qui permet d’empiler les décorateurs :

interface Coffee {
    fun cost(): Int
    fun description(): String
}
class Espresso : Coffee {
    override fun cost() = 200
    override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 50
    override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 30
    override fun description() = inner.description() + ", syrup"
}

Dans main :

val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")

Sortie :

espresso, milk, milk, syrup: 330

Lorsque les décorateurs modifient le résultat, leur ordre est important : chacun travaille sur ce que l’objet qui se trouve à l’intérieur de lui a renvoyé :

interface Text {
    fun render(): String
}
class Word(private val w: String) : Text {
    override fun render() = w
}
class Bracket(private val inner: Text) : Text {
    override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
    override fun render() = inner.render() + inner.render()
}

Dans main :

println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())

Résultat :

[hi][hi]
[hihi]

Avec by, un décorateur n’écrit que les membres qu’il modifie et transmet automatiquement le reste :

interface Source {
    fun read(): String
    fun name(): String
}
class FileSource(private val content: String) : Source {
    override fun read() = content
    override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
    override fun read() = inner.read().replace(word, "***")
}

Dans main :

val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")

Sortie :

notes.txt: the *** plan

Les décorateurs sont combinés pendant l’exécution du programme, par exemple à partir des choix d’un client, tandis qu’une sous-classe fixe une combinaison lors de l’écriture du code. Trois extras sous forme de sous-classes nécessiteraient une classe pour chaque combinaison ; sous forme de décorateurs, fold les empile à partir d’une liste :

interface Coffee {
    fun cost(): Int
}
class Espresso : Coffee {
    override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
    override fun cost() = inner.cost() + price
}

À l’intérieur de main :

val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())

Sortie :

370
challenge icon

Défi

Facile

Les pizzas sont construites avec des décorateurs de l’interface fournie Pizza (avec price() et toppings()). Écrivez Base (800, sans garnitures) ainsi que les décorateurs Cheese (+150, ajoute cheese), Olives (+100, ajoute olives) et DoubleUp, qui double le prix de tout ce qui se trouve à l’intérieur et répète deux fois ses garnitures. Écrivez ensuite build(names), qui part de Base et l’enveloppe avec un décorateur par nom, dans l’ordre (cheese, olives ou double).

Le code fourni lit des lignes contenant des noms séparés par des virgules, ou plain, construit chaque pizza et affiche 1050: cheese, olives ou 800: plain.

Votre code doit être placé dans Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt et Pizza.kt. Main.kt contient le code d’entrée/sortie fourni 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()
    for (line in input) {
        val names = if (line == "plain") listOf() else line.split(",")
        val pizza = build(names)
        val toppings = pizza.toppings()
        println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
    }
}
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