[Kotlin] by와 Delegate Pattern

강승구·2024년 3월 16일

Delegate Pattern에 대한 이해

delegate는 위임한다는 의미를 갖는다. 어떤 일을 다른 이에게 떠넘기는 것이다. Delegate Pattern은 어떤 기능을 자신이 수행하지 않고 다른 객체가 수행하도록 하는 패턴이다. 즉, 기능을 위임시키는 것이다.

Delegate Pattern을 설명할 땐 상속과 구성 (Inheritance & Composition) 의 차이에 대한 이야기가 필연적이다. 상위 클래스의 요소들을 사용할 때에는 상속 혹은 구성을 사용할 수 있다.
상속은 모든 요소를 물려받기 때문에 변수나 메소드 등을 다시 구현할 필요가 없어 편리하지만, 객체의 유연성이 떨어진다는 치명적인 단점이 있다. 하지만 만약 상위 클래스가 변경된다면, 하위 클래스 역시 반드시 고쳐져야 한다.

따라서 이러한 경우 유연성을 높이기 위해, 구성 (Composition) 관계를 통해 상위 클래스를 이용하는 것을 권장한다. 상속이 아닌 객체 소유로써 상위 클래스의 요소를 활용하는 것이다.

Delegate Pattern 은 구성 (Composition) 을 활용한 패턴이다. 구성을 통해 특정 객체를 소유하고, 모든 동작들을 소유하고 있는 객체에게 모두 위임하는 형식이다.

상속과 구성의 차이

상속과 구성의 차이는 다음과 같이 조금 더 자세하게 정의할 수 있다.

  • 상속 : 클래스의 변수와 메서드를 모두 받기 때문에 재구현할 필요가 없어서 편리하다는 장점이 있지만 객체의 유연성이 떨어진다는 단점이 있다.
  • 구성 : 구성은 상속의 단점을 보완하기 위한 기법으로 클래스가 다른 클래스의 객체를 멤버 데이터로 포함하는 기능이다.

아래 예제에서는 Car라는 인터페이스를 상속받는 Kia와 Hyundae 클래스가 있다.

interface Car {
    val tire: String
    val engine: String
    fun manufacture()
}

class Kia: Car {
    override val tire: String
        get() = "Kia 타이어"
    override val engine: String
        get() = "Kia 엔진"

    override fun manufacture() {
        println("${tire}${engine}만든 Car 완성")
    }
}

class Hyundae(private val premiumCar: Car): Car {
    override val tire: String
        get() = premiumCar.tire
    override val engine: String
        get() = premiumCar.engine

    override fun manufacture() {
        println("${tire}${engine}만든 Car 완성")
    }
}

Kia와 Hyundae 클래스는 모두 Car라는 인터페이스를 상속받았다. 하지만 Kia클래스는 클래스 내부에서 프로퍼티를 직접 초기화지만 Hyundae 클래스는 객체를 생성하는 쪽에서 프로퍼티를 초기화한다.
즉 객체를 생성하는 쪽에게 초기화를 위임하는 것이다.

예를 들어, A 클래스는 B클래스의 객체를 멤버로 포함할 수 있다. 여기서 B에 정의된 모든 public 메서드는 클래스 A의 내부에서 실행될 수 있다.
위의 예제에서 Car 인터페이스를 구현하는 Hyundae 클래스의 생성자로 premiumCar를 전달하는 행위가 구성에 해당된다.

정리하자면 Delegate Pattern이란 어떤 기능을 자신이 처리하지 않고 다른 객체에 위임시켜 그 객체가 일을 처리하도록 하는 것이다.


by

위의 예제에서 Hyundae 내부에서 필요한 작업을 내부에서 직접 처리하는 작업은 보일러 플레이트 코드로 분류할 수 있다.
Delegate Pattern 을 사용할 때 모든 메소드에 대해 일일히 Wrapper 메소드를 작성해줘야 함을 알 수 있다.
코틀린에서는 by라는 키워드를 제공해 이러한 보일러 플레이트 코드를 줄일 수 있다.

아래처럼 by 키워드를 사용하게 되면, 컴파일러가 자동으로 Delegate Pattern 코드를 작성해주게 된다. 따라서 일일히 Car 의 메소드들을 구현해줄 필요가 없다.

class Test(private val premiumCar: Car): Car by premiumCar
profile
강승구

0개의 댓글