Patrón decorador
Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 47 de 57.
El patrón decorador añade comportamiento a un objeto envolviéndolo en otro objeto con la misma interfaz. El decorador delega en el objeto que envuelve y añade su propia parte, por lo que los decoradores se pueden apilar:
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"
}Dentro de main:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")Salida:
espresso, milk, milk, syrup: 330Cuando los decoradores cambian el resultado, su orden importa: cada uno trabaja sobre lo que devolvió el objeto que está dentro de él:
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()
}Dentro de main:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())Salida:
[hi][hi]
[hihi]Con by, un decorador escribe solo los miembros que cambia y reenvía automáticamente el resto:
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, "***")
}Dentro de main:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")Salida:
notes.txt: the *** planLos decoradores se combinan mientras se ejecuta el programa, por ejemplo a partir de las elecciones de un cliente, mientras que una subclase fija una combinación cuando se escribe el código. Tres extras como subclases requerirían una clase para cada combinación; como decoradores, fold los apila a partir de una lista:
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
}Dentro 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())Salida:
370Desafío
FácilLas pizzas se construyen con decoradores de la interfaz proporcionada Pizza (con price() y toppings()). Escribe Base (800, sin ingredientes) y los decoradores Cheese (+150, añade cheese), Olives (+100, añade olives) y DoubleUp, que duplica el precio de todo lo que contiene y repite sus ingredientes dos veces. Después escribe build(names), que comienza con Base y lo envuelve con un decorador por cada nombre, en orden (cheese, olives o double).
El código proporcionado lee líneas de nombres separados por comas, o plain, construye cada pizza e imprime 1050: cheese, olives o 800: plain.
Tu código va en Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt y Pizza.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()
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(", "))
}
}
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
1Clases y objetos
Trabajar con archivosDefinir clasesConstructores primariosBloques init y validaciónConstructores secundariosRepaso - Tarjeta de biblioteca4Herencia
Clases abiertas y subclasesSobrescritura de miembrossuper y constructoresClases abstractasRepaso: directorio del personal2Propiedades y encapsulación
Accesores personalizadosModificadores de visibilidadSetters privadoslazy y lateinitRepaso: cuenta bancaria5Interfaces
Declaración de interfacesMétodos predeterminadosInterfaces múltiplesInterfaces como tiposRepaso: métodos de pago8Genéricos
Funciones genéricasClases genéricasRestricciones de tiposVariancia con in y outRepaso - Cola de prioridad11Proyecto: Gestión de biblioteca
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Kotlin