[HeadFirst] Kotlin Class 상속

timothy jeong·2021년 10월 23일
0

코틀린

목록 보기
6/20

superClass, subClass

superClass 는 상위 클래스, subClass 는 하위 클래스를 의미한다. 이 둘을 연결하는게 상속(inheritance) 이다. subClass 는 superClass 의 메서드와 property 를 갖는다.

Any

Kotlin 의 모든 클래스들의 superClass 최상위에는 Any class 가 존재한다. 즉, Kotlin 클래스 상속체계의 뿌리라고 할 수 있다. Any 클래스는 equals(), hashCode(), toString() 메서드가 정의되어 있으며, 따라서 모든 kotlin 클래스는 세개의 메서드를 가지고 있다.

Inheritance

그렇다면 상속은 왜 사용하는 걸까?
어플리케이션 규모가 커지면서, 각 class 간에 중복되는 code 를 하나의 클래스에서 관리하기 위해 사용한다. 따라서 일반적으로 common code 는 superClass 에 존재한다.

상속체계 디자인

상속 체계를 디자인할때 가장 기본적인건 IS-A, HAS-A 분석이다. 예를 들어 hippo IS-A animal 이건 즉, hippo 의 superClass 가 animal 인것을 의미한다. 하지만 animal 이 반드시 hippo 의 직속 superClass 여야한다는 것은 아니다. animal 은 hippo 의 superClass 의 superclass 일 수 있다.

kitchen 과 fridge 는 어떨까? 둘은 분명 관계가 있지만 IS-A 관계가 아니다. 이럴땐 kitchen HAS-A fridge 로 보는게 더 정확하다. 이때 둘은 상속관계라기 보다는 kitchen 클래스가 fridge 라는 property 를 갖는게 자연스럽다.

IS-A 분석에 부합하지 않지만, code 중복을 줄이기 위해 상속을 사용하는건 어떨까? 개념상 일치하지 않는 두 객체를 상속관계로 묶는건 부적절하다. 차라리 둘의 공통되는 속성을 갖는 새로운 객체를 만들어 둘이 모두 상속하게 하는것이 바람직 할 것이다.

상속 구현

기본적으로 kotlin 클래스는 상속을 할 수 없는 상태(final) 이다. 상속이 가능한 클래스로 정의하기 위해서는 open 이라는 키워드를 사용해야한다. open 키워는 property 와 메서드에도 사용하여 override 대상임을 컴파일러에게 알려주어야 한다.

superClass 에서 val 로 정의된 변수를 subClass 에서 변경하기 위해서는 open 키워드를 붙여줘야한다. 그러면 subClass 는 override 키워드를 통해 변경할 수 있다.

open class Animal {
    open val image = ""
    open val food = ""
    open val habitat = ""
    var hunger = 10

    open fun makeNoise() {
        println("make some noise")
    }

    open fun eat() {
        println("eat")
    }

    open fun roam() {
        println("roaming")
    }

    fun sleep() {
        println("ZZZZ...")
    }
}

one superClass

class 를 상속할때는 하나의 superClass 만 가질 수 있다. multi superClass 는 불가능하다는 것이다.

override

상속하는 클래스는 : 를 통해서 상속한다.
val 는 override 키워드를 이용해 변경할 수 있고, var 는 init 을 이용해서 값을 정의해야한다. 만약 init 으로 값을 정의하지 않으면 subClass 에서 사용할 수 없다.

superClass 에서 val 로 정의된 property 는 override 를 통해 var 로 재정의 할 수 있다.

함수를 override 할때 함수 파라미터를 일치시키고, 함수의 반환 타입의 호환에 신경써야한다. 즉, 반환 타입을 일치시키거나 subclass 타입으로 해야한다는 것이다.

cclass Hippo : Animal() {
    override var image = "hippo.jpg"
    override val food = "grass"
    override val habitat = "water"

    init {
        hunger = 11
    }

    override fun eat() { 
        if (hunger < 0) { 
            println("Hippo do not need to eat $food.")
            return 
        }
        
        println("The Hippo is eating $food")
        hunger-- 
    } 
}

val->var / var->val

superClass 에서 val 로 정의된 property 를 var 로 변경할 수 있다. 이는 superClass 와 subClass 간에 protocol 인 superClass 에서 할 수 있는것을 subClass 에서 할 수 있어야 한다 를 지키기 때문에 가능하다.

더 자세하게 이야기하자면 val 로 선언된 변수에는 컴파일러가 암묵적으로 getter 를 추가한다. subClass 에서 이를 var 로 바꾸는 것은 setter 를 추가하는 것이기 때문에 superClass 에서 할 수 있는 getter 를 여전히 subClass 에서 할 수 있다.

반대로 superClass 에 var 로 정의된 변수를 subClass 에서 val 로 바꿀 수 없는데, 이는 superClass 에서 할 수 있는 setter 를 subClass 에서 할 수 없게 되기 때문이다.

이 protocol 지켜져야 하는 이유는 다형성(polymorphism) 때문이다.

fun main() {
    val myAnimal: Animal = Hippo()
    myAnimal.eat() // The Hippo is eating grass
}

superType(superClass) 로 선언된 변수에는 subClass 를 받을 수 있어야하기 때문에, 해당 protocol 이 지켜져야 한다.

다형성에 대해서는 다른 포스트에서 더 자세하게 다룬다. kotlin 다형성 포스팅

open & final

이러한 규칙을 지키면서 Hippo Class 는 Animal Class 를 상속하였다. 상속체계에서 한번 open 된 property, 메서드들 하위 클래스에서 계속 open 된 상태가 유지된다. 그러다가 final 키워드를 만나면 하위 Class 에서 부터는 final 상태가 된다.

superClass 에서 open 된 property 나 메서드만 subClass 에 사용되는게 아니다. open 된 상태라는 것은 override 가능한 상태라고 이해해야한다.

open 되지 않았다고 하더라도 subClass 에서 해당 property, 메서드에 접근할 수 있으며 이때는 상위 superClass 중에서 가장 먼저 override 되거나 선언된 property, 메서드에 접근하게 된다.

profile
개발자

0개의 댓글