위임

위임

상속 말고도 코드를 재사용하는 확실한 방법이 하나 더 있어요. 바로 위임(Delegation) 패턴인데요, Kotlin은 이 패턴을 언어 차원에서 지원해서 보일러플레이트 코드 없이도 자연스럽게 쓸 수 있어요. 어떻게 동작하는지 살펴볼게요.

출처: Kotlin 공식 문서

본문

위임 패턴은 구현 상속(implementation inheritance)의 좋은 대안으로 입증됐어요. Kotlin은 이 패턴을 네이티브로 지원해서 보일러플레이트 코드가 전혀 필요 없죠.

클래스 Derived는 자신의 public 멤버를 모두 특정 객체에 위임해서 인터페이스 Base를 구현할 수 있어요.

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val base = BaseImpl(10)
    val derived = Derived(base)

    derived.print()
    // 10
}

Derived의 상위 타입 목록에 있는 by 절은 bDerived의 객체 내부에 저장되고, 컴파일러가 Base의 모든 메서드를 b로 전달(forward)하도록 생성한다는 뜻이에요.

위임으로 구현한 인터페이스의 멤버 오버라이드

오버라이드는 예상대로 동작해요. 컴파일러는 위임 객체의 구현 대신 여러분이 작성한 override 구현을 사용하죠. 다음 예시처럼 Derivedoverride fun printMessage() { print("abc") }를 추가하면, printMessage가 호출될 때 10 대신 abc가 출력돼요.

interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { println("abc") }
}

fun main() {
    val base = BaseImpl(10)
    val derived = Derived(base)

    derived.printMessage()
    // abc
    derived.printMessageLine()
    // 10
}

단, 이렇게 오버라이드된 멤버는 위임 객체의 멤버로부터 호출되지 않는다는 점을 주의해야 해요. 위임 객체는 자기 자신의 인터페이스 멤버 구현에만 접근할 수 있거든요.

interface Base {
    val message: String
    fun print()
}

class BaseImpl(x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // 이 속성은 b의 print() 구현에서 접근할 수 없어요
    override val message = "Message of Derived"
}

fun main() {
    val base = BaseImpl(10)
    val derived = Derived(base)

    derived.print()
    // BaseImpl: x = 10
    println(derived.message)
    // Message of Derived
}

더 알아보기