본문 바로가기
Kotlin

[Kotlin] open class와 abstract class (feat. 코틀린에서의 final)

by 태크민 2024. 11. 2.

코틀린에서의 final

코틀린 클래스는 기본적으로 final이며 이는 상속이 불가능하게 한다. 만약 상속이 가능하게 하려면 open키워드를 써야한다.

자바에서는 final 로 명시적으로 상속을 금지하지 않는다면 기본적으로 모든 클래스를 다른 클래스가 상속할 수 있다. 하지만, 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기는 경우도 많다.

 

객체 지향적인 관점에서 객체가 있고, 해당 객체에 대한 코드를 줄이기 위해 재사용 가능하다면 재사용 하는 것이 좋지만, 이러한 사용 방식은 상속하는 기반 클래스가 변경이 없는 경우에만 유효하다.

기반 클래스가 변경이 잦은데, 무분별하게 클래스를 상속하게 된다면, 취약한 기반 클래스(fragile base class) 문제에 직면하게 된다. 기반 클래스가 변경될 때마다 기반 클래스를 상속하는 모든 자식 클래스들은 변경되어야 하며, 어느 부분에 문제가 생길지 모른다.

취약한 기반 클래스(fragile base class)

취약한 기반 클래스(Frail or Fragile Base Class) 문제는, 하위 클래스가 기반 클래스의 동작에 대해 가졌던 가정이 기반 클래스의 변경으로 인해 깨질 때 발생한다.

기반 클래스가 자신을 상속하는 방식에 대해 명확한 규칙이나 가이드를 제공하지 않는 경우, 하위 클래스는 기반 클래스 작성자의 의도와는 다른 방식으로 메서드를 오버라이드할 수 있으며, 이로 인해 예기치 않은 동작이 발생할 위험이 있다.

특히, 하위 클래스의 수나 구현 방식은 기반 클래스 작성자가 모두 파악하기 어렵기 때문에, 기반 클래스의 코드를 수정할 경우 의도치 않게 하위 클래스의 동작이 변경되거나 오류가 발생할 수 있다. 이러한 측면에서 기반 클래스는 변경에 취약하며, 이 문제를 취약한 기반 클래스 문제라고 부른다.

 

예를 들어

open class Animal {
    open fun makeSound() {
        println("Some generic sound")
    }

    fun sleep() {
        println("Animal sleeps")
    }
}
class Dog : Animal() {
    override fun makeSound() {
        println("Woof")
    }
}
  • Animal 클래스의 makeSound()는 하위 클래스에서 재정의할 수 있는 구조이다.
  • 그런데 어느 날, Animal의 작성자가 코드를 이렇게 수정했다고 가정해보자
open class Animal {
    open fun makeSound() {
        println("Some generic sound")
        logSound()  // 추가됨!
    }

    private fun logSound() {
        println("Sound was made")
    }
}
  • 겉보기엔 아무 문제 없어 보이지만, Dog 클래스의 makeSound()가 오버라이드 되어 있으므로 logSound()는 호출되지 않게 된다!
  • 이러면 Animal 클래스 작성자는 "makeSound 후에 로그도 남기겠지?" 라고 생각했지만, 실제로는 하위 클래스에서는 로그가 전혀 남지 않을 수 있다.

즉, 기반 클래스의 변경이, 하위 클래스의 동작을 깨트린 것이다.

 

왜 위험할까?

  • 하위 클래스가 많아질수록 기반 클래스 변경 시 리스크가 커진다.
  • 따라서, 기반 클래스는 의도치 않게 하위 클래스의 동작을 깨트릴 수 있는 "취약한" 존재가 되는 것이다.
  • 특히, 기반 클래스는 모든 하위 클래스의 동작을 예측하거나 테스트하는 건 현실적으로 어렵다.
  • 즉, 기반 클래스는 변경보다는 확장에 강해야 하는데, 그렇지 못할 때 이런 문제가 생길 수 있다.

이러한 문제를 해결하기 위해 코틀린에서는 class의 기본 상속 변경자를 final로 설정하였다. 

자바의 클래스와 메서드는 기본적으로 상속에 열려있지만 코틀린의 클래스와 메서드는 기본적으로 final이다.
어떤 클래스의 상속을 허용하려면 클래스 앞에 open 변경자를 붙여야한다.
오버라이드를 허용하고 싶은 메서드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.

 

Clickable.kt
interface Clickable {
    fun click()
    fun showOff() = println("I'm clicked") // default 구현이 있는 메서드
}
RickButton.kt
open class RichButton : Clickable { // 이 클래스는 열려있다. 다른 클래스가 이 클래스를 상속할 수 있다.
    fun disable() {}  // default final 이다. 하위 클래스가 이 메서드를 오버라이드 할 수 없다.
    open fun animate() {} // 하위클래스가 이 메서드를 오버라이드 할 수 있다.
    final override fun click() {} // 이 함수는 상위 클래스의 열려있는 메서드를 오버라이드한다. 오버라이드한 메서드는 기본적으로 열려있다.
}

 

1) open class 클래스명

open class RichButton : Clickable {

 

이 클래스는 다른 클래스가 상속할 수 있다.

2) 아무것도 선언되어있지 않은 default 는 final 이다.

fun disable() {}

 

3) open fun 메서드명

open fun animate() {}

 

하위 클래스가 이 메서드를 오버라이드 할 수 있다.

 

4) final override fun 메서드명

final override fun click() {}

 

final 이 없는 override 메서드나 프로퍼티는 기본적으로 열려있다. 따라서 오버라이드 하지못하게 금지하려면 오버라이드하는 메서드 앞에 final을 명시해야한다.

 


abstact 클래스

추상클래스를 생성한다.

 

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

 

1) abstract class 클래스명

추상클래스다.

 

2) abstract fun 메서드명

추상 함수다. 이 함수에는 구현이 없으며, 하위 클래스에서는 반드시 오버라이드 해야하는 메서드다.

 

3) oepn fun 메서드명

추상클래스에 속했더라도, 비추상함수는 기본적으로 final 이다. 하지만 원한다면 open 을 사용하여 오버라이드를 허용할 수 있다.

 

인터페이스 멤버의 경우
인터페이스 멤버의 경우 final, open, abstract 를 사용하지 않는다. 인터페이스 멤버는 항상 열려있으며 final 로 변경할 수 없다.인터페이스 멤버에게 본문이 없으면 자동으로 추상 멤버가 되지만, 그렇더라도 따로 멤버 선언 앞에 abstract 키워드를 덧붙일 필요가 없다.

 

클래스 내의 상속 제어 변경자 의미

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

 

 

끝.


참고자료

https://devfunny.tistory.com/756

 

[Kotlin in Action] 14. 코틀린의 가시성 변경자 (open, abstract, internal)

코틀린에서의 final 자바에서는 final 로 명시적으로 상속을 금지하지 않는 모든 클래스를 다른 클래스가 상속할 수 있다. 이렇게 기본적으로 상속이 가능하면 편리한 경우도 많지만 문제가 생기

devfunny.tistory.com

https://jslee-tech.tistory.com/49

 

(Kotlin) open class, abstract class

Java에는 final이라는 키워드가 있다. final로 선언된 클래스와 메소드는 기본적으로 상속과 오버로드가 제한된다. 즉, 자식 클래스에서 이 클래스나 메소드를 재정의하려 할 때 컴파일 에러를 발생

jslee-tech.tistory.com

https://kotlinworld.com/68

 

Kotlin의 상속 변경자 : final, abstract, open

목적 Kotlin의 상속 변경자가 어떻게 설계되었는지 이유와 의미를 이해한다. 개요 Java에서는 class는 기본적으로 상속이 가능했다. 상속을 불가능하게 만들기 위해서는 final 변경자를 붙여야 했다.

kotlinworld.com

'Kotlin' 카테고리의 다른 글

[Kotlin] 컬렉션(Collection) 함수  (9) 2024.11.04
[Kotlin] by란?  (0) 2024.11.02
[Kotlin] 깊은 복사 (Deep Copy) 3가지 방법  (0) 2024.11.02
[Kotlin] val vs Const val  (0) 2024.11.02
[Kotlin] Object 키워드 (with companion object)  (0) 2024.11.02