Menu
CoddyTech

Mẫu Strategy

Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 45 trên 57.

Mẫu strategy đặt mỗi biến thể của một thuật toán vào lớp riêng, dùng chung một giao diện. Code sử dụng mẫu này sẽ giữ một chiến lược và có thể thay thế chiến lược đó khi chương trình đang chạy:

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

Bên trong main:

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

Đầu ra:

108
121

Khi một chiến lược chỉ có một phương thức, dùng kiểu hàm là đủ. Lớp lưu một hàm và gọi hàm đó:

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

Bên trong main:

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

Đầu ra:

HELLO WORLD
olleh dlrow

Chiến lược thay thế when bên trong thuật toán. Với when, mỗi lựa chọn mới đều đòi hỏi chỉnh sửa thuật toán. Với các chiến lược, một lựa chọn mới là một lớp hoặc lambda mới, còn Checkout không bao giờ thay đổi:

// Không có chiến lược: mỗi tùy chọn mới đều phải sửa hàm (function) này
fun cost(kind: String, kg: Int) = when (kind) {
    "standard" -> 5 + kg
    else -> 15 + kg * 2
}
// Có chiến lược: thêm một lớp (class), không cần sửa Checkout
class Overnight : Shipping {
    override fun cost(kg: Int) = 40
}

Một chiến lược thường được chọn dựa trên dữ liệu, chẳng hạn như gói dịch vụ của khách hàng, bằng một bản đồ hoặc một nhà máy nhỏ:

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

Bên trong main:

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

Đầu ra:

160
180
200
challenge icon

Thử thách

Dễ

Đòn tấn công của anh hùng là một chiến lược. Giao diện Attack (có name và damage(strength)) đã được cung cấp. Hãy viết Sword (sức mạnh sát thương nhân 2), Bow (sức mạnh cộng 5) và Magic(mana) (sức mạnh nhân 3 khi mana ít nhất là 10, nếu không thì là 1). Hero(name, strength, attack) có thể đổi attack bất cứ lúc nào, còn strike() trả về Ada hits with sword for 20 mà không kiểm tra mình đang có đòn tấn công nào.

Code được cung cấp đọc name,strength, sau đó đọc các lệnh equip bow, equip magic 15, equip sword hoặc strike, rồi in ra kết quả của mỗi đòn tấn công.

Code của bạn nằm trong Sword.kt, Bow.kt, Magic.kt, Hero.kt và Attack.kt. Main.kt chứa code đầu vào/đầu ra đã được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

fun main() {
    // Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
    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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Kotlin