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
121Khi 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 dlrowChiế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) ?: priceBên trong main:
println(finalPrice("gold", 200))
println(finalPrice("silver", 200))
println(finalPrice("basic", 200))Đầu ra:
160
180
200Thử 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()
}
}
}
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
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpHàm khởi tạo chínhKhối init và kiểm tra tính hợp lệHàm khởi tạo phụÔn tập: thẻ thư viện4Kế thừa
Lớp mở và lớp conGhi đè thành viênsuper và hàm khởi tạoLớp trừu tượngÔn tập: Danh bạ nhân viên2Thuộc tính và tính đóng gói
Trình truy cập tùy chỉnhBộ sửa đổi phạm vi truy cậpSetter riêng tưlazy và lateinitÔn tập: tài khoản ngân hàng5Giao diện
Khai báo giao diệnPhương thức mặc địnhNhiều giao diệnGiao diện như kiểu dữ liệuÔn tập: Phương thức thanh toánTự luyện tập: Playground Kotlin