Menu
Coddy logo textTech

Patrón Strategy

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

El patrón de estrategia coloca cada variante de un algoritmo en su propia clase detrás de una interfaz compartida. El código que lo utiliza mantiene una estrategia y puede reemplazarla mientras el programa se ejecuta:

interface Shipping {
    fun cost(kg: Int): Int
}
class Standard : Shipping {
    override fun cost(kg: Int) = 5 + kg
}
class Express : Shipping {
    override fun cost(kg: Int) = 15 + kg * 2
}

class Checkout(var shipping: Shipping) {
    fun total(price: Int, kg: Int) = price + shipping.cost(kg)
}

Dentro de main:

val checkout = Checkout(Standard())
println(checkout.total(100, 3))
checkout.shipping = Express()
println(checkout.total(100, 3))

Salida:

108
121

Cuando una estrategia tiene un solo método, basta con un tipo de función. La clase almacena una función y la llama:

class Formatter(var style: (String) -> String) {
    fun show(words: List<String>) = words.joinToString(" ") { style(it) }
}

Dentro de main:

val f = Formatter { it.uppercase() }
println(f.show(listOf("hello", "world")))
f.style = { it.reversed() }
println(f.show(listOf("hello", "world")))

Salida:

HELLO WORLD
olleh dlrow

Una estrategia reemplaza un when dentro del algoritmo. Con el when, cada nueva opción implica editar el algoritmo. Con las estrategias, una nueva opción es una nueva clase o lambda, y Checkout nunca cambia:

// sin una estrategia: cada nueva opción edita esta función
fun cost(kind: String, kg: Int) = when (kind) {
    "standard" -> 5 + kg
    else -> 15 + kg * 2
}
// con una estrategia: añade una clase, deja Checkout en paz
class Overnight : Shipping {
    override fun cost(kg: Int) = 40
}

Una estrategia suele elegirse a partir de los datos, como el plan de un cliente, mediante un mapa o una pequeña fábrica:

val discounts: Map<String, (Int) -> Int> = mapOf(
    "gold" to { price -> price * 80 / 100 },
    "silver" to { price -> price * 90 / 100 }
)

fun finalPrice(plan: String, price: Int) = discounts[plan]?.invoke(price) ?: price

Dentro de main:

println(finalPrice("gold", 200))
println(finalPrice("silver", 200))
println(finalPrice("basic", 200))

Salida:

160
180
200
challenge icon

Desafío

Fácil

El ataque de un héroe es una estrategia. Se proporciona la interfaz Attack (con name y damage(strength)). Escribe Sword (daño igual a strength multiplicado por 2), Bow (strength más 5) y Magic(mana) (strength multiplicado por 3 cuando mana es como mínimo 10; de lo contrario, 1). Hero(name, strength, attack) puede cambiar su attack en cualquier momento, y strike() devuelve Ada hits with sword for 20 sin comprobar qué ataque tiene.

El código proporcionado lee name,strength y después los comandos equip bow, equip magic 15, equip sword o strike, e imprime el resultado de cada ataque.

Tu código va en Sword.kt, Bow.kt, Magic.kt, Hero.kt y Attack.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: mantenlo como está
    val input = generateSequence(::readLine).toList()
    val (name, strength) = input[0].split(",")
    val hero = Hero(name, strength.toInt(), Sword())
    for (cmd in input.drop(1)) {
        val p = cmd.split(" ")
        if (p[0] == "strike") {
            println(hero.strike())
            continue
        }
        hero.attack = when (p[1]) {
            "bow" -> Bow()
            "magic" -> Magic(p[2].toInt())
            else -> Sword()
        }
    }
}
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