Menu
Coddy logo textTech

Strategy Pattern

Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 45 of 57.

The strategy pattern puts each variant of an algorithm in its own class behind a shared interface. The code that uses it holds one strategy and can replace it while the program runs:

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

Inside main:

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

Output:

108
121

When a strategy has a single method, a function type is enough. The class stores a function and calls it:

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

Inside main:

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

Output:

HELLO WORLD
olleh dlrow

A strategy replaces a when inside the algorithm. With the when, every new option means editing the algorithm. With strategies, a new option is a new class or lambda, and Checkout never changes:

// without a strategy: every new option edits this function
fun cost(kind: String, kg: Int) = when (kind) {
    "standard" -> 5 + kg
    else -> 15 + kg * 2
}
// with a strategy: add a class, leave Checkout alone
class Overnight : Shipping {
    override fun cost(kg: Int) = 40
}

A strategy is often chosen from data, such as a customer's plan, with a map or a small factory:

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

Inside main:

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

Output:

160
180
200
challenge icon

Challenge

Easy

A hero's attack is a strategy. The interface Attack (with name and damage(strength)) is supplied. Write Sword (damage strength times 2), Bow (strength plus 5) and Magic(mana) (strength times 3 when the mana is at least 10, otherwise 1). Hero(name, strength, attack) can change its attack at any time, and strike() returns Ada hits with sword for 20 without checking which attack it has.

The supplied code reads name,strength and then commands equip bow, equip magic 15, equip sword or strike, and prints the result of each strike.

Your code goes in Sword.kt, Bow.kt, Magic.kt, Hero.kt and Attack.kt. Main.kt holds the supplied input/output code and cannot be edited.

Try it yourself

fun main() {
    // Supplied input/output code: keep it as it is
    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 iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Kotlin playground