Menu
Coddy logo textTech

동적 디스패치

Coddy Kotlin 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 27번째.

다형성은 하나의 호출이 여러 방식으로 동작할 수 있음을 의미합니다. 슈퍼클래스 또는 인터페이스 타입을 통해 오버라이드된 메서드를 호출하면, Kotlin은 프로그램이 실행되는 동안 객체의 실제 클래스를 확인하고 해당 클래스의 버전을 실행합니다. 이를 동적 디스패치라고 합니다.

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

main 내부에서:

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

출력:

Rex: Woof
Bess: Moo
Fido: Woof

새로운 하위 클래스가 나타나도 호출 코드는 변경되지 않습니다. 아래의 totalArea는 모든 Shape을 대상으로 작성되었으므로, 아무도 이를 변경하지 않았지만 Triangle에서도 작동합니다:

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

main 내부에서:

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

출력:

10.0

디스패치는 변수가 아니라 객체를 살펴봅니다. 같은 변수에 다른 객체를 할당하면 다음 호출에서 실행되는 코드가 바뀝니다:

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() + "!"
}

main 내부:

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

출력:

hello
HELLO!

다형성은 동물을 사용하는 곳마다 반복될 수 있는 if (kind == "dog")과 같은 검사를 대체합니다. 각 클래스는 자체 동작을 유지하며, 새로운 종류의 동물을 추가한다는 것은 클래스 하나를 추가한다는 의미입니다:

// 다형성 없이: 모든 함수가 kind를 확인한다
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// 다형성으로: 새로운 kind가 자체 동작을 가져온다
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

챌린지

쉬움

Character(name, hp)는 open 클래스입니다. takeHit(damage)는 피해량을 hp에서 빼며(0 미만으로 내려가지 않음), status()는 hp가 0일 때 Ada: 30 hp 또는 Ada: down을 반환합니다. hp는 어디서나 읽을 수 있지만 Character 내부에서만 변경할 수 있습니다. takeHit을 재정의하고 변경된 피해량을 super.takeHit에 전달하여 하위 클래스를 완성하세요. Warrior(40 hp)는 피해량을 절반으로 줄이고 내림 처리합니다. Mage(25 hp)는 첫 번째 공격 전체를 흡수하는 방패를 가집니다. Rogue(30 hp)는 받는 공격 중 두 번째마다(2번째, 4번째, ...) 회피합니다.

제공된 코드는 파티를 warrior:Ada,mage:Bo,rogue:Cy로 읽은 다음 all 10 또는 Ada 12 공격을 적용합니다. Character 타입을 통해 takeHit을 호출하고 마지막으로 모든 status()를 출력합니다.

코드는 Character.kt, Warrior.kt, Mage.kt 및 Rogue.kt에 작성합니다. Main.kt에는 제공된 입력/출력 코드가 들어 있으며 편집할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입출력 코드: 그대로 유지하세요
    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 icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: Kotlin 플레이그라운드