프로퍼티 위임 - 코틀린

10565 단어 코틀린코틀린

프로퍼티 위임

일반적인 프로퍼티들은 매번 필요할 때 마다 그 값을 수동적으로 구현하게 되는데 이걸 한번만 구현한 다음 라이브러리화 시켜 추후에 재사용 할 수 있다면 훨씬 도움이 많이 될 것이다. 코틀린에서 이런 편리한 기능을 위해 프로퍼티 위임 (Delegated properties)를 지원 해준다.

  • Lazy properties: 객체의 생성시점에 초기화 되는것이 아닌 처음 접근할 때 초기화를 시켜준다.
  • Observable properties: 리스너는 해당 프로퍼티의 값 변경에 대해 알림을 받는다.
  • 각 프로퍼티들을 별도로 저장하는것이 아닌 Map에 프로퍼티를 저장할 수 있게 한다.

이런 케이스들에도 적용할 수 있게 코틀린은 프로퍼티 위임을 지원해준다.

그럼 어떻게 사용할 수 있을까? 기본 문법은 다음과 같다.

class Example {
	var p: String by Delegate()
}
  • val / var <propertiy name>: <Type> by <expression>.

by 뒤에 명시해주는 expression이 p의 구현을 대신해주는 위임자가 된다. Delegate 클래스가 p 프로퍼티의 get(), set()을 대치해 줄 getValue()와 setValue()메서드를 구현해야 하기 때문이다.

실제로 위 기본문법처럼 구현하게 되면

  • 위임받는 Delegate클래스에서 getValue와 setValue가 구현되어 있지 않아 Error message가 나타나는것을 볼 수 있다. 그래서 위임받는 Delegate클래스에는 다음 예시처럼 getValue와 setValue를 구현 해줘야한다.
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

getValue와 setValue는 컨텍스트를 받는 thisRef와 KProperty타입을 받는 property, 그리고 set을 위한 value파라미터를 갖게된다.

  • Kproperty는 kotlin.reflect 패키지에 선언된 인터페이스로 val or var로 선언된 프로퍼티를 나타내는 인터페이스로 설명되어있다. KProperty의 인스턴스는 :: 연산자로 얻을 수 있는데 자세한건 다음에 정리할 예정이다..

여튼 Kproperty는 다시 KCallable 인터페이스를 상속 받기 때문에 property의 이름이나 타입의 값을 추출 할 수 있게 된다.

실제 Delegate 클래스와 프로퍼티 위임으로 선언된 p 프로퍼티를 자바로 디컴파일된 코드를 확인 해보면

  • p 프로퍼티가 Delegate 타입으로 변경되고 getP와 setP 메서드에서 Delegate 객체에 접근해 getValue와 setValue를 호출 하는것을 알 수 있다.
  • 또한 Kproperty타입의 delegatedProperties 배열을 생성해 Reflection을 이용해 프로퍼티 속성을 얻고 파라미터로 넘겨주는것을 확인할 수 있다.

그래서 p값을 읽게되면 delegate 객체의 getValue()메서드가 동작해 p의 값을 얻을 수 있게되고 p에 값을 쓰게되면 delegate 객체의 setValue()메서드를 통해 값을 쓸 수 있게 된다.

만약 2개의 프로퍼티를 생성하고 프로퍼티에 접근 할 때마다 로그를 찍어야 하는 경우가 있다고 가정해보면 Delegate 패턴을 사용하는것이 좋다. 다음 예시를 보자

만약 token프로퍼티와 attemps 프로퍼티에 접근 할 때마다 로그를 찍어야한다면 ?

var token:String
	get() = {
			print (~~~)
				}
	set(value) = {
		print (~~~)
				}
var attemps: Int
	get() = {
			print (~~~)
				}
	set(value) = {
		print (~~~)
				}
  • 이렇게 각각 프로퍼티의 접근자에 로그를 찍는 메서드를 추가해 줘야 하는데 이걸 공통화 해서 재사용 가능하게 구현하면 훨씬 편리할 것이다.
var token: String? by LoggingProperty(null)
var attemps: Int by LoggingProperty(10)

class LoggingProperty<T>(var value: T) {
    operator fun getValue(
        thisRef: Any?,
        prop: KProperty<*>
    ): T {
        print("${prop.name} returned value $value")
        return value
    }
    operator fun setValue(
        thisRef: Any?,
        prop: KProperty<*>,
        newValue: T
    ) {
        val name = prop.name
        print("$name changed from $value to $newValue")
        value = newValue
    }
}

이렇게 프로퍼티 위임을 이용해 공통적으로 필요한 로직을 추출해 재사용 가능하게 구현 하였다. 실제로 프로젝트에서 단순 로깅뿐만 아니라 더 복잡한 요구사항이 있을 경우에도 프로퍼티 위임을 이용해서 사용한다면 더 편리할 것이다.

다음엔 이어서 Lazy 프로퍼티와 Observable 프로퍼티에 대해서 정리 할 계획이다.

참고 :

https://kotlinlang.org/docs/delegated-properties.html

이펙티브 코틀린 도서

좋은 웹페이지 즐겨찾기