Kotlin 위임 (Delegation)


위임 구현 (Implementation by Delegation)

kotlin 은 상속의 대안이 되는 Delegation pattern을 boilerplate code 없이 지원한다.

아래 예제에서, Derived 클래스는 자신의 모든 public 멤버들을 특정 객체(BaseImpl)로 위임하여 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 b = BaseImpl(10)
    Derived(b).print()
}

Output:

10

Derived 클래스의 상위 타입 목록 안에 쓰인 bybDerived의 객체 안에 내부적으로 저장될 것이고, 컴파일러는 b로 보내는 Base의 모든 메소드들을 생성할 것임을 나타낸다. 이 예제에서 알 수 있듯이 키워드 by은 위임하는 기능을 제공한다.

또 다른 예를 보도록 하자. 아래 예제에서는 Base 인터페이스를 구현하는 BaseImpl 클래스가 있다고 하자. BaseImpl 클래스의 기능들이 좋아사 BaseImpl의 일부를 일시적으로 확장 기능을 이용하고 싶은 경우에 Base 인터페이스가 요구하는 속성과 함수를 모두 다시 정의하는 것이 아니라 일부 기능만을 확장한 BaseImplEx 클래스를 작성하여 일시적으로 사용할 수 있다.

interface Base {
    fun funcA()
    fun funcB()
}

class BaseImpl : Base {
    override fun funcA() {
        println("AAA")
    }

    override fun funcB() {
        println("BBB")
    }
}

class BaseImplEx(b: Base) : Base by b {
    override fun funcA() {
        println("AAA!!!")
    }
}

fun main() {
    println("== BaseImpl ====")
    val b = BaseImpl()
    b.funcA()
    b.funcB()

    println("== BaseImplEx ====")
    val bx = BaseImplEx(b)
    bx.funcA()
    bx.funcB()
}

Output:

== BaseImpl ====
AAA
BBB
== BaseImplEx ====
AAA!!!
BBB

위임에 의해 구현된 인터페이스의 멤버 재정의 (Overriding a member of an interface implemented by delegation)

재정의는 예상대로 작동한다: 컴파일러는 위임 객체의 재정의된 구현체 보다는 다시 재정의된 구현체를 사용한다. 즉, Derived에 재정의 함수 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() { print("abc") }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}

Output:

abc10

그러나 이러한 방식으로 재정의된 멤버는 인터페이스 멤버의 자체 구현에만 액세스할 수 있는 위임 객체의 멤버에서 호출되지 않는다.

즉, 위임 객체의 멤버들은 인터페이스 멤버들의 구현들만 접근이 가능하다.

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 b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}

Output:

BaseImpl: x = 10
Message of Derived

위임 속성 (Delegated Properties)

위 예제는 Class Delegation 이고, Property Delegation 으로 속성에 대한 액세스를 다른 클래스에 위임 할 수도 있다.

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()
    override fun toString() = "Example"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "-- Get ${prop.name} from $thisRef"
    }
    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        println("-- Set \"$value\" to ${prop.name} in $thisRef")
    }
}

fun main() {
    val e = Example()
    e.p = "NEW STRING"    // -- Set "NEW STRING" to p in Example
    println(e.p)          // -- Get p from Example
}

참조