본문 바로가기

스터디/다재다능 코틀린 프로그래밍

8. 클래스 계층과 상속

다재다능 코틀린 프로그래밍 책으로 코틀린 스터디를 진행하면서 발표를 위해 준비했던 글입니다.


인터페이스와 추상 클래스 생성

인터페이스 만들기

interface Remote {
    fun up()
    fun down()
    fun doubleUp() {
        up()
        up()
    }
}
// default 키워드 없이 메소드 구현 가능, 재정의 가능

인터페이스 구현

class TV {
    var volume = 0
}
// 생성자 뒤에 콜론으로 구현하고있음을 명시
class TVRemote(val tv: TV) : Remote {
    override fun up() { tv.volume++ }
    override fun down() { tv.volume-- }
}

컴패니언 객체와 인터페이스

interface Remote {
    ...
    companion object {
        fun combine(first: Remote, second: Remote): Remote = object: Remote {
            override fun up() {
                first.up()
                second.up()
            }

            override fun down() {
                first.down()
                second.down()
            }
        }
    }
}

추상클래스 생성

abstract class Animal(val name: String) {
    abstract fun bark(): String
}
class Dog(name: String) : Animal(name) {
    override fun bark() = "$name: 멍멍!"
}

인터페이스와 추상클래스의 차이와 선택 기준은 자바와 같다.

중첩 클래스와 내부 클래스

  • 위에서 살펴봤던 TV와 TVRemote(구현 클래스)를 분리할 경우
    • 장점
      • TV 인스턴스 하나에 여러개의 TVRemote를 가질 수 있으니 연결 관계를 아낄 수 있다.
      • TV 인스턴스와 분리된 채로 내부 상태를 가질 수 있다.
    • 단점
      • TV 인스턴스에 대한 참조를 추가로 가지고있어야한다.
      • TV 인스턴스 내부를 활용할 수 없고 public 메소드만 활용할 수 있다.
  • 내부 클래스를 사용하면 장점을 살리고 단점을 버릴 수 있다.
    • TV 인스턴스의 참조도 필요 없고 내부도 사용할 수 있게 됨.
  • 코틀린은 중첩 클래스도 외부 클래스의 private 멤버에 접근X 하지만 내부 클래스를 사용하면 제약이 사라진다.

내부 클래스 생성

class TV {
    private var volume = 0
    val remote: Remote
    get() = TVRemote()
    inner class TVRemote: Remote {
        override fun up() {
            volume++;
        }
        override fun down() {
            volume--
        }
    }
}

내부 클래스 this@, super@

  • 내부 클래스 속성이나 메소드가 외부 클래스의 멤버와 이름이 일치한다면 특별한 this 표현식 사용 가능
    • ${this@TV.toString()}
  • 내부 클래스 메소드에서 TV의 베이스 클래스인 Any.toString()에 접근하고 싶다면 super 표현식 사용 가능
    • ${super@TV.toString}
    • 베이스 클래스로 바이패스 해서 오류가 많아지고 다형성을 깨뜨리므로 사용하지 말자.

getter로 익명 내부 클래스 사용하기

class TV {
    private var volume = 0
    val remote: Remote
        get() = object: Remote {
            override fun up() { volume++; }
            override fun down() { volume-- }
            override fun toString() = "${this@TV.toString()}"   
        }

    override fun toString() = "TV 볼륨: $volume"
}

상속

  • 코틀린 클래스는 디폴트가 final
  • open이라고 명시된 클래스, 메소드만 상속 가능
  • 자식 클래스는 override로 명시해줘야 한다.
  • final override를 명시 후 재정의 하면 이후 서브 클래스가 해당 메소드를 재정의할 수 없다.
  • val로 정의된 속성 → var, val 재정의 가능
  • var로 정의된 속성 → var만 재정의 가능
    • val로 재정의하면서 setter를 내부적으로 제거할 수 없기 때문
  • 베이스 클래스의 private나 protected 멤버를 자식 클래스에서는 public으로 만들 수 있다.
    • 반대의 경우인 베이스 클래스의 public 멤버를 자식 클래스의 private나 protected로는 만들 수 없다.

씰드 클래스

  • 동일한 파일에 작성된 다른 클래스들만 sealed class 확장 가능
sealed class Card(val suit: String)
class Ace(suit: String) : Card(suit)
class King(suit: String) : Card(suit)
  • sealed class의 생성자는 private으로 취급된다.
  • 자식 클래스는 sealed 클래스의 싱글톤 객체가 될 수 있다.
  • sealed 클래스의 객체는 생성할 수 없지만 자식 객체들은 생성할 수 있다.

enum의 생성과 사용

enum class Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES
}

sealed class Card(val suit: Suit)
class Ace(suit: Suit) : Card(suit)
class King(suit: Suit) : Card(suit)
  • enum 메소드에 open 키워드를 붙이면 enum 인스턴스에서 open 메소드를 재정의할 수 있다.
  • enum 메소드를 추상메소드로 만들 수도 있다. 각각의 인스턴스에서 추상메소드를 구현해야 한다.