Menu
Coddy logo textTech

Strategyパターン

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 45/57。

Strategyパターンでは、アルゴリズムの各バリエーションを、共通インターフェースの背後にある独自のクラスに配置します。それを使用するコードは1つのStrategyを保持し、プログラムの実行中に置き換えることができます:

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

mainの中:

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

出力:

108
121

戦略が単一のメソッドを持つ場合は、関数型で十分です。クラスは関数を格納して呼び出します。

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

mainの中では次のようにします。

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

出力:

HELLO WORLD
olleh dlrow

ストラテジーは、アルゴリズム内の when を置き換えます。when を使う場合、新しいオプションを追加するたびにアルゴリズムを編集する必要があります。ストラテジーを使えば、新しいオプションは新しいクラスまたはラムダになり、Checkout は決して変更されません。

// ストラテジーなし: 新しいオプションごとにこの関数を編集する
fun cost(kind: String, kg: Int) = when (kind) {
    "standard" -> 5 + kg
    else -> 15 + kg * 2
}
// ストラテジーあり: クラスを追加し、Checkout はそのままにする
class Overnight : Shipping {
    override fun cost(kg: Int) = 40
}

戦略は、顧客のプランなどのデータに基づいて、マップまたは小さなファクトリから選択されることがよくあります。

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

main の中では次のようにします。

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

出力:

160
180
200
challenge icon

チャレンジ

簡単

英雄の攻撃は戦略です。インターフェース Attack(name と damage(strength) を持つ)が提供されています。Sword(ダメージは strength の2倍)、Bow(strength に5を加えた値)、Magic(mana)(mana が10以上の場合は strength の3倍、それ以外は1)を作成してください。Hero(name, strength, attack) はいつでも自身の attack を変更でき、strike() はどの攻撃を持っているかを確認せずに Ada hits with sword for 20 を返します。

提供されたコードは name,strength を読み取り、その後 equip bow、equip magic 15、equip sword、または strike のコマンドを受け取り、各 strike の結果を出力します。

コードは Sword.kt、Bow.kt、Magic.kt、Hero.kt、および Attack.kt に記述してください。Main.kt には提供された入力/出力コードが含まれており、編集できません。

自分で試してみよう

fun main() {
    // 提供された入出力コード: そのままにしておく
    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 icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Kotlinプレイグラウンド