Menu
CoddyTech

Gọi phương thức động

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 27 trên 57.

Tính đa hình có nghĩa là một lời gọi có thể hoạt động theo nhiều cách. Khi bạn gọi một phương thức được ghi đè thông qua kiểu lớp cha hoặc kiểu giao diện, Kotlin sẽ kiểm tra lớp thực tế của đối tượng khi chương trình chạy và thực thi phiên bản của lớp đó. Đây là điều phối động:

abstract class Animal(val name: String) {
    abstract fun sound(): String
    fun speak() = "$name: ${sound()}"
}
class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}
class Cow(name: String) : Animal(name) {
    override fun sound() = "Moo"
}

Bên trong main:

val barn: List<Animal> = listOf(Dog("Rex"), Cow("Bess"), Dog("Fido"))
for (a in barn) println(a.speak())

Đầu ra:

Rex: Woof
Bess: Moo
Fido: Woof

Đoạn code gọi hàm không thay đổi khi có lớp con mới. totalArea bên dưới được viết để dùng với mọi Shape, nên nó cũng hoạt động với Triangle, dù không ai thay đổi nó:

interface Shape {
    fun area(): Double
}
class Square(val side: Double) : Shape {
    override fun area() = side * side
}
class Triangle(val base: Double, val height: Double) : Shape {
    override fun area() = base * height / 2
}

fun totalArea(shapes: List<Shape>) = shapes.sumOf { it.area() }

Bên trong main:

println(totalArea(listOf(Square(2.0), Triangle(3.0, 4.0))))

Đầu ra:

10.0

Việc điều phối dựa vào đối tượng, không phải biến. Gán một đối tượng khác cho cùng một biến sẽ thay đổi đoạn code chạy ở lần gọi tiếp theo:

interface Mode {
    fun format(text: String): String
}
class Normal : Mode {
    override fun format(text: String) = text
}
class Shout : Mode {
    override fun format(text: String) = text.uppercase() + "!"
}

Bên trong main:

var mode: Mode = Normal()
println(mode.format("hello"))
mode = Shout()
println(mode.format("hello"))

Đầu ra:

hello
HELLO!

Tính đa hình thay thế các bước kiểm tra như if (kind == "dog"), vốn sẽ lặp lại ở mọi nơi sử dụng động vật. Mỗi lớp giữ hành vi riêng, và để thêm một loại động vật, bạn chỉ cần thêm một lớp:

// Không dùng tính đa hình: mỗi hàm (function) đều kiểm tra loại
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// Dùng tính đa hình: loại mới có hành vi riêng
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

Thử thách

Dễ

Character(name, hp) là một lớp mở. takeHit(damage) trừ sát thương khỏi hp (không bao giờ thấp hơn 0), còn status() trả về Ada: 30 hp hoặc Ada: down khi bằng 0. Bạn có thể đọc hp ở mọi nơi, nhưng chỉ có thể thay đổi nó bên trong Character. Hoàn thành các lớp con bằng cách ghi đè takeHit và truyền sát thương đã thay đổi cho super.takeHit: Warrior (40 hp) nhận một nửa sát thương, làm tròn xuống; Mage (25 hp) có lá chắn hấp thụ toàn bộ đòn đánh đầu tiên; Rogue (30 hp) né mọi đòn đánh thứ hai nhận được (lần thứ 2, thứ 4, ...).

Code được cung cấp đọc nhóm nhân vật theo dạng warrior:Ada,mage:Bo,rogue:Cy, sau đó tấn công bằng all 10 hoặc Ada 12. Code gọi takeHit thông qua kiểu Character và cuối cùng in ra mọi status().

Đặt code của bạn trong Character.kt, Warrior.kt, Mage.kt và Rogue.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 party = input[0].split(",").map { spec ->
        val (kind, name) = spec.split(":")
        when (kind) {
            "warrior" -> Warrior(name)
            "mage" -> Mage(name)
            else -> Rogue(name)
        }
    }
    for (cmd in input.drop(1)) {
        val (target, damage) = cmd.split(" ")
        for (c in party) {
            if (target == "all" || target == c.name) c.takeHit(damage.toInt())
        }
    }
    for (c in party) println(c.status())
}
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