본문 바로가기
기타

프로퍼티, 그리고 위임

by 나이아카 2022. 10. 26.

 코틀린에는 Delegate라는 기능이 존재합니다(물론 코틀린에만 존재하는 개념은 아닙니다). 저는 처음에 코틀린에서 observable을 사용하기 위해 쓰다가 처음 발견하게 되었습니다. 이때는 delegate pattern이라는 것을 잘 몰라서 그냥 저것 하나만 사용했었는데, 어느날 프로퍼티 위임과 delegate pattern의 존재를 알고 나니 기존 프로젝트에서 사용하는 것을 꺼리게 되었습니다. 그래서 언젠가 정리해둬야겠다 생각은 했었는데 이제야 정리하는 느낌입니다.


 먼저 프로퍼티 위임에 대해 알아보기 위해서는 프로퍼티가 무엇인지, 그리고 필드를 무엇으로 정의하는지 알고 가야 합니다. 왜냐하면 코틀린에서 프로퍼티란 필드와 이에 접근할 수 있는 접근자를 묶어 표현하는 것이기 때문입니다.

 

프로퍼티와 필드

public class Person {
    private final String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public void setAge(int age) {
        this.age = aeg;
    }

    public boolean getAge() {
        return this.age;
    }
}

 자바에서 흔히 볼 수 있는 변수를 저장하는 클래스의 모습입니다. 이클립스나 인텔리J에서는 클래스를 생성하고 난 후, data class처럼 사용하고 싶을 때 게터와 세터를 자동으로 완성시켜주는 기능까지 존재하고 있을 정도로 당연하고 흔한 패턴입니다. 여기서 field는 name과 age가 되고, 접근자는 필드에 접근할 수 있다는 의미로 getName(), setAge(), getAge()가 됩니다. 그리고 name과 getName(), 그리고 age와 setAge(), getAge()를 묶어 각각을 프로퍼티라고 부릅니다.

data class Person(val name, var age)

 위와 같은 자바 코드를 코틀린에서는 이렇게 사용할 수 있습니다. 코틀린 코드만 보면 name과 age는 필드이고 접근자는 따로 없다고 생각할 수 있으나, 코틀린은 기본적으로 변수를 생성할 때 getter와 setter를 숨겨서 제공해줍니다.

 위와 같이 선언된 데이터 클래스를 자바에서 이용하는 경우 getName()과 getAge(), setAge()로 변수를 불러올 수 있습니다. 이는 자바는 기본적으로 필드 기반으로 작성되지만, 코틀린은 편의를 위해 게터와 세터를 기본적으로 제공하는 프로퍼티의 형식으로 되어있기 때문입니다.

 위임이란?

 프로그래밍에서 위임이란, 객체가 직접 작업을 수행하지 않고 다른 객체가 그 작업을 대신 처리하는 것을 의미합니다. 이때 대신 수행하는 객체를 Delegate Class라고 합니다.

}

/**
 * Base interface that can be used for implementing property delegates of read-write properties.
 *
 * This is provided only for convenience; you don't have to extend this interface
 * as long as your property delegate has methods with the same signatures.
 *
 * @param T the type of object which owns the delegated property.
 * @param V the type of the property value.
 */
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V

    /**
     * Sets the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @param value the value to set.
     */
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

 코틀린에서 by Delegates.~ 코드에서 볼 수 있는 ReadWriteProperty 코드입니다. 이 코드는 getValue와 setValue를 가진 인터페이스로 이 코드를 이용해서 getter와 setter의 정의를 내릴 수 있습니다.

private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

 위의 코드는 Delegates 내부에 있는 코드로 nullable하지 않은 var 타입을 반환시켜주는 형태의 class입니다. 여기서는 인터페이스가 readWrite를 위해 만들어졌기 때문에 getter와 setter를 가지고 있지만, 만약 다른 방식으로 데이터를 가공하기 위해 printNameAndValue() 라는 메소드를 만들면 인터페이스를 가진 클래스에서 printNameAndValue() 메소드를 재정의하는 것으로 그 처리를 할 수 있게 됩니다.

 이런식으로 필드에 접근하는 로직을 인터페이스에 위임하는 것을 위임 프로퍼티라고 하며, 코틀린에서는 by keyword를 통해 좀 더 편리하게 프로퍼티에 위임을 사용할 수 있도록 돕고 있습니다.(ex: private val data : Int by Delegates.observable(0))

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()
}

 위의 코드는 by 절을 이용해서 delegate 하는 방법에 대한 코드 예제입니다.


 이러한 위임 프로퍼티를 사용하는 이유는, 동일한 interface를 상속받아 다른 class가 생성되더라도 같은 interface를 공유하면 동일한 코드로 다른 결과를 이끌어내 코드의 재사용과 유지보수성을 높이기 위함입니다. 제대로 이용하면 코드 분석이 빨라지기도 하고, 각 클래스들이 어떤 역할을 하는지 명확하게 파악하는 것도 도움을 줄 수 있겠습니다. 하지만 제대로 이용하지 못한다면 결국 불필요한 코드를 추가적으로 생산해내는 것이라(이는 모든 패턴, 아키텍쳐가 동일할 것 같습니다.) 모든 이론들은 제대로 이해한 후, 코드에 적용하는 것이 필요합니다.

 저는 아직 코틀린에서 제공해주는 수준의 위임 정도만 사용하지만, 추후에 여유가 된다면 제작중인 앱들에게도 이러한 패턴을 적용해 더 효율적으로 코드를 작성할 수 있으면 좋을 것 같습니다.

댓글