(TIL) Kotlin - Delegated Property

Posted by Eun JongHyeok on March 16, 2025
  1. 기본 개념
    1. 위임(Delegation)이란?
    2. 왜 Delegated Property를 사용할까?
  2. Delegated Property의 구현 원리
    1. operator fun getValue
    2. operator fun setValue
  3. 간단한 Delegated Property 예제
  4. 표준 라이브러리의 Delegated Property
    1. by lazy
    2. by observable
    3. by vetoable
  5. 언제 Delegated Property를 사용해야 할까?
  6. 결론

TIL은 가벼운 개념정리 목적으로 작성되어 있습니다.


Kotlin의 Delegated Property는 프로퍼티의 gettersetter 동작을 다른 객체에 위임(delegate)하는 기능입니다.
이 기능을 통해 코드의 재사용성과 간결성을 높이고, 여러 상황(예: lazy 초기화, 변경 감지, 캐싱 등)에서 부가적인 로직을 손쉽게 추가할 수 있습니다.


기본 개념

위임(Delegation)이란?

  • 위임은 객체의 일부 작업을 다른 객체에 맡기는 디자인 패턴입니다.
  • Kotlin에서는 by 키워드를 사용하여 프로퍼티의 접근(읽기/쓰기) 동작을 다른 객체에 위임할 수 있습니다.
  • 결과적으로, 프로퍼티는 직접 값을 저장하지 않고 대신 위임 객체가 값을 관리합니다.

왜 Delegated Property를 사용할까?

  • 코드 재사용 및 간결성:
    공통 로직(예: lazy 초기화, 변경 감지, 동기화 등)을 여러 곳에서 사용할 때, 해당 기능을 별도 클래스에 구현해 두면 중복 코드를 줄일 수 있습니다.
  • 부가 기능 추가:
    값 읽기, 쓰기 시 로깅, 검증, 캐싱 등의 부가 작업을 쉽게 추가할 수 있습니다.
  • 표준 라이브러리 지원:
    Kotlin은 by lazy, by observable, by vetoable 등의 내장 delegated property를 제공하여 반복되는 작업을 줄이고 생산성을 높입니다.

Delegated Property의 구현 원리

Delegated Property를 사용하려면, 위임 객체가 다음 두 operator 함수를 구현해야 합니다.

operator fun getValue

  • 역할: 프로퍼티가 읽힐 때 호출됩니다.
  • 매개변수:
    • thisRef: 위임받은 프로퍼티가 속한 객체(또는 null).
    • property: 프로퍼티의 메타정보(KProperty<*>) 제공.
1
2
3
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    return 42 // 항상 42를 반환하는 단순 예제
}

operator fun setValue

  • 역할: 프로퍼티에 값을 할당할 때 호출됩니다.
  • 매개변수:
    • thisRef: 위임받은 프로퍼티가 속한 객체.
    • property: 프로퍼티의 메타정보.
    • value: 새롭게 할당될 값.
1
2
3
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    println("New value is $value")
}

이 두 함수를 구현하면, 프로퍼티의 접근과 수정 작업이 모두 위임 객체에 의해 제어되므로 추가적인 로직(예: 로그 기록, 값 검증 등)을 쉽게 삽입할 수 있습니다.


간단한 Delegated Property 예제

다음은 기본적인 예제입니다.
Example 클래스의 number 프로퍼티는 내부적으로 Delegate 객체에 의해 관리됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        println("${property.name} is accessed")
        return 42
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        println("Setting ${property.name} to $value")
    }
}

class Example {
    var number: Int by Delegate()
}

fun main() {
    val example = Example()
    println(example.number) // "number is accessed" 출력 후 42 반환
    example.number = 100    // "Setting number to 100" 출력
}

이 예제를 통해 프로퍼티 접근 시마다 위임 객체의 getValuesetValue가 호출됨을 확인할 수 있습니다.


표준 라이브러리의 Delegated Property

Kotlin 표준 라이브러리는 이미 다양한 Delegated Property를 제공합니다.

by lazy

  • 지연 초기화(lazy initialization)를 지원합니다.
  • 프로퍼티에 처음 접근할 때 초기화되며, 이후에는 초기화된 값을 재사용합니다.
  • 주로 val과 함께 사용됩니다.
1
2
3
4
val lazyValue: String by lazy {
    println("Computing value...")
    "Hello, World!"
}

by observable

  • 값이 변경될 때마다 콜백을 통해 변경 사항을 감지합니다.
1
2
3
var observedValue: Int by Delegates.observable(0) { property, oldValue, newValue ->
    println("${property.name} changed from $oldValue to $newValue")
}

by vetoable

  • 값 변경 전에 조건을 검사하여, 변경을 허용할지 결정합니다.
1
2
3
var vetoableValue: Int by Delegates.vetoable(0) { _, old, new ->
    new >= old  // 새로운 값이 이전 값보다 크거나 같을 때만 변경 허용
}

언제 Delegated Property를 사용해야 할까?

Delegated Property는 여러 상황에서 매우 유용합니다. 아래는 몇 가지 사용 사례와 권장 상황입니다.

  • 공통 로직의 재사용이 필요한 경우
    • 여러 프로퍼티에서 동일한 변경 감지, 검증, 혹은 캐싱 로직이 필요한 경우 공통 로직을 한 곳에 모아 중복 코드를 줄이고, 유지보수를 쉽게 할 수 있습니다.
  • Lazy Initialization이 필요한 경우
    • 리소스가 많이 드는 객체나, 초기화 비용이 큰 값을 프로퍼티로 관리할 때 by lazy를 사용하면 처음 접근할 때만 초기화되므로 불필요한 초기화를 방지할 수 있습니다.
  • 값 변경 시 부가 작업이 필요한 경우
    • 값이 변경될 때마다 로깅, 데이터 동기화, UI 업데이트 등의 추가 작업을 수행해야 할 때 delegated property를 사용하면, getter/setter 내부에 부가 로직을 쉽게 추가할 수 있습니다.
  • 외부 라이브러리와 연동할 때
    • 상태 관리 라이브러리(예: Jetpack Compose의 mutableStateOf)와 연계하여, UI 갱신이나 기타 동작을 자동으로 처리할 때 외부 라이브러리에서 이미 제공하는 delegated property 기능을 활용하면, 코드가 훨씬 간결해집니다.
  • 커스텀 Delegate를 구현해야 할 때
    • 특정 도메인 로직이나 특별한 비즈니스 규칙이 필요한 경우, 자신만의 delegate를 구현해 재사용할 수 있습니다. 특정 기능을 캡슐화하여 다른 클래스에서도 쉽게 사용할 수 있도록 함으로써, 코드의 모듈화와 가독성을 높입니다.

결론

Kotlin Delegated Property는 프로퍼티의 get과 set 동작을 다른 객체에 위임하여,

  • 코드 재사용성을 높이고
  • 부가 기능(예: lazy 초기화, 변경 감지, 검증 등)을 간편하게 추가할 수 있게 해줍니다.

또한,

  • 공통 로직의 재사용이나 복잡한 값 변경 처리, 또는 리소스 최적화가 필요할 때는 Delegated Property를 적극 활용하는 것이 좋습니다.
  • Kotlin의 표준 delegate(by lazy, by observable, by vetoable)를 사용하면 이미 검증된 패턴을 쉽게 적용할 수 있으며, 필요한 경우 커스텀 delegate로 확장하여 사용하면 됩니다.

Android
kotlin
delegated_property
by_lazy
by_observable
by_vetoable

← Previous Post