본문 바로가기

개발 이야기/Kotlin

[Kotlin] Delegate Pattern 이란? by 키워드 사용하기

Delegate Pattern 이란?

Delegate Pattern을 알기 전에 Delegator와 Delegate 용어를 정리해보자.

 

Delegator는 어떤 행위를 다른이에게 위임하는 위임자, Delegate는 어떤 행위를 위임받아 실제로 수행하는 대리자를 의미한다. 따라서 Delegate Pattern이란 어떤 기능을 자신이 수행하지 않고 다른 객체에 위임하여 해당 객체가 일을 수행하도록 구성한 디자인 패턴이다.

Delegate Pattern이 필요한 이유는?

Delegate Pattern의 필요성을 이해하기 위해서는 상속(Inheritance)구성(Composition)에 대한 내용을 함께 이해해야 한다. 흔하게 상속은 is-a 관계라고 말하는데(Hero class가 SuperMan class의 부모 class인 경우, SuperMan is a Hero 관계가 성립됨), 이 경우 클래스의 변수와 메소드 등을 상속받아서 재구현할 필요가 없다.

 

하지만 이런 상속관계는 아주 제한적으로 신중하게 사용해야 한다. 그 이유는 정적 타입 언어에서 상속은 가장 결합성(의존성)이 큰 편이기 때문에, 두 클래스 관계의 유연성이 떨어지기 때문이다. 만약 필요하지 않은 속성은 굳이 상속받지 않아도 되거나, 부모 class의 일부 기능만 재사용하고 싶다면 상속은 결코 좋은 방법이 될 수 없다.

 

따라서 반드시 상속을 사용해야하는 상황이 아니라면 Composition(또는 Aggregation) 관계(has-a 관계)로 구현하는 것을 권장하는데, Delegate PatternComposition 관계를 사용하는 대표적인 패턴이다.

Kotlin by 키워드 사용하기

Delegate Pattern을 쉽게 제공하는 것이 Kotlinby 키워드이다.

interface Base {
    fun print()
}

// delegate
class BaseImpl1(val x: Int) : Base {
    override fun print() { println("BaseImpl1 $x") }
}

// delegate
class BaseImpl2(val x: Int) : Base {
    override fun print() { println("BaseImpl2 $x") }
}

// delegator
class Derived(b: Base) : Base {
    var b: Base = b

    override fun print() {
        b.print()
    }
}

fun main() {
    val b1 = BaseImpl1(10)
    Derived(b1).print()

    val b2 = BaseImpl2(10)
    Derived(b2).print()
}

위 코드는 by 키워드에 의해서 boilerplate code 상당 부분 없앤 아래 코드로 변경이 가능하다.

interface Base {
    fun print()
}

// delegate
class BaseImpl1(val x: Int) : Base {
    override fun print() { println("BaseImpl1 $x") }
}

// delegate
class BaseImpl2(val x: Int) : Base {
    override fun print() { println("BaseImpl2 $x") }
}

// delegator
class Derived(b: Base) : Base by b

fun main() {
    val b1 = BaseImpl1(10)
    Derived(b1).print()

    val b2 = BaseImpl2(10)
    Derived(b2).print()
}

위 코드를 조금 쉽게 생각해보자. Base 인터페이스를 따르는 Derived 클래스는 같은 인터페이스를 구현하는 Base 클래스의 객체 b의 구현을 기본으로 작업을 수행한다. 즉, Derived는 Base 인터페이스의 함수를 꼭 구현할 필요가 없고, 인터페이스 함수를 호출할 경우는 주입받은 b의 함수를 호출한다.