본문 바로가기
Language/Kotlin

[Kotlin IN ACTION] - Kotlin open / final / abstract 상속 제어(접근) 변경자

by Jman 2022. 7. 22.

open 변경자

자바에서는 final 을 사용한 클래스는 상속이 안된다는 걸 알고 있을 것이다.

반대로 말을하자면, final 을 안 쓴 클래스는 모든 클래스가 상속 받을 수가 있다.

하지만 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만, 문제가 생기는 경우가 많다.

 

취약 기반 클래스(fragile base class) 라는 문제가 있다.

그게 무엇이냐면, 하위클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스를 변경함으로써 깨져버린 경우에 생긴다.

 

어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙(어떤 메서드를 어떻게 오버라이드해야 하는지 등)을 제공하지 않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메서드를 오버라이드할 위험이 있다.

 

모든 하위 클래스를 분석하는 것은 불가능하다. 따라서 기반 클래스를 변경하는 경우 하위 클래스의 동작이 예기치 않게 바뀔 수 있다는 면에서 기반 클래스는 '취약'하다.

* 기반 클래스 : 파생 클래스에 모든 메서드와 변수를 물려주는 조상 클래스라 생각하면 된다.

 

 

즉, 상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라 조언하고, 특별히 하위 클래스에 오버라이드하게 의도된 클래스와 메서드가 아니라면 모두 final 로 만들라는 말이다.

 

자 여기서 다시 코틀린 문법을 보자.

모틀린도 마찬가지 철학을 따른다.

 

자바의 클래스와 메서드는 기본적으로 상속에 대해 열려있지만, 코틀린의 클래스와 메서드는 기본적으로 final 이다.

 

어떤 클래스의 상속을 허용하려면, 클래스 앞에 open 변경자를 붙여야 한다. 

그와 더불어 오버라이드를 허용하고 싶은 메서드나 프로퍼티의 앞에도 open 변경자를 붙여야 한다.

 

// 열린 메서드를 포함한 열린 클래스 정의하기
open class RichButton : Clickable { // 이 클래스는 열려있다. 다른 클래스가 이 클래스를 상속할 수 있다.

    fun disable() {} // 이 함수는 파이널이다. 하위 클래스가 이 메서드를 오버라이드할 수 없다. 
    
    open fun animate() {} // 이 함수는 열려있다. 하위 클래스에서 이 메서드를 오버라이드해도 된다.
    
    override fun click() {} // 이 함수는 (상위 클래스에서 선언된) 열려있는 메서드를 오버라이드 한다.
                            // 오버라이드 한 메서드는 기본적으로 열려있다.
}

 

코드 주석이 달려있지만 설명을 덧붙이자면, 기반 클래스나 인터페이스의 멤버를 오버라이드하는 경우 그 메서드는 기본적으로 열려 있다.

 

오버라이드하는 메서드 구현을 하위 클래스에서 오버라이드하지 못하게 금지하려면 오버라이드하는 메서드 앞에 final 을 명시해야 한다.

// 오버라이드 금지하기
open class RichButton : Clickable {
	final override fun click() {}
}

 

* 열린 클래스와 스마트 캐스트

클래스의 기본적인 상속 가능 상태를 final 로 함으로써 얻을 수 있는 큰 이익은 다양한 경우에 스마트 캐스트가 가능하다는 점이다.

스마트 캐스트는 타입 검사 뒤에 변경될 수 없는 변수에만 적용 가능하다.

클래스 프로퍼티의 경우 이는 val 이면서 커스텀 접근자가 없는 경우에만 스마트 캐스트를 쓸 수 있다는 의미다.
즉, 프로퍼티가 final 이어야만 한다.

프로퍼티가 final 이 아니라면 그 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의함으로써 스마트 캐스트의 요구사항을 깰수가 있다.

 

 

abstract 변경자

자바처럼 abstract 로 선언한 추상 클래스는 인스턴스화 할 수 없다.

 

추상 클래스에는 구현이 없는 추상 멤버가 있기 때문에 하위 클래스에서 그 추상 멤버를 오버라이드해야만 하는 게 보통이다.

 

추상 멤버는 항상 열려(open)있다. 따라서, 추상 멤버 앞에 open 변경자를 명시할 필요는 없다.

 

abstract class Animated { // 추상 클래스. 인스턴스를 만들 수 없다.
    
    abstract fun animate() // 추상 함수다. 이 함수는 구현이 없고, 하위 클래스에서는 이 함수를 반드시 오버라이드 해야 한다.
    
    // 아래 두 비추상 함수는 추상 클래스에 속했더라도 기본적으로 파이널이지만, 원한다면 open 으로 오버라이드를 허용할 수 있다.
    open fun stopAnimating() {}
    fun animateTwice() {}
}

 

클래스 내에서 상속 제어 변경자 정리

변경자 이 변경자가 붙은 멤버는... 설명
final 오버라이드 X 클래스 멤버의 기본 변경자
open 오버라이드 O 반드시 open을 명시해야 오버라이드 가능
abstract 반드시 오버라이드 O 추상 클래스의 멤버에만 이 변경자를 붙일 수 있고, 추상 멤버에는 구현이 있으면 안된다.
override 상위 클래스나 상위 인스턴스의 멤버를 오버라이드 하는 중을 의미 오버라이드 하는 멤버는 기본적으로 열려있다. 하위 클래스의 오버라이드를 금지하려면 final을 명시해야 한다.