본문 바로가기
개발&프로그래밍

[Kotlin] 위임된 프로퍼티(Delegated Properties)

by 재아군 2024. 8. 17.

Kotlin의 위임된 프로퍼티(Delegated Properties)

Kotlin의 위임된 프로퍼티(Delegated Properties)는 프로퍼티의 getter와 setter를 다른 객체에 위임할 수 있는 강력한 기능입니다.

이 가이드에서는 위임된 프로퍼티의 다양한 측면을 상세히 살펴보겠습니다.

 

Kotlin의 위임된 프로퍼티(Delegated Properties)

 

 

기본 개념 (Basic Concept)

위임된 프로퍼티는 다음과 같은 문법으로 선언합니다:

class Example {
    var p: String by Delegate()
}

 

여기서 by 키워드 다음에 오는 표현식이 델리게이트입니다. 프로퍼티의 get()(그리고 set())은 델리게이트의 getValue()setValue() 메서드에 위임됩니다.

 

import kotlin.reflect.KProperty

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.")
    }
}

 

 

표준 델리게이트 (Standard Delegates)

Kotlin 표준 라이브러리는 몇 가지 유용한 델리게이트를 제공합니다.

 

 

지연 프로퍼티 (Lazy Properties)

lazy() 함수를 사용하여 지연 초기화를 구현할 수 있습니다:

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

이 코드는 첫 번째 접근 시에만 "computed!"를 출력하고, 그 이후에는 저장된 값을 반환합니다.

 

 

관찰 가능한 프로퍼티 (Observable Properties)

Delegates.observable()을 사용하여 프로퍼티 변경을 관찰할 수 있습니다:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

 

 

다른 프로퍼티에 위임 (Delegating to Another Property)

프로퍼티는 다른 프로퍼티에 위임될 수 있습니다:

var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt
    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt

이 기능은 예를 들어 하위 호환성을 유지하면서 프로퍼티 이름을 변경할 때 유용할 수 있습니다.

 

 

맵에 프로퍼티 저장 (Storing Properties in a Map)

프로퍼티 값을 맵에 저장하는 것은 JSON 파싱 등의 동적 작업에서 흔히 사용됩니다:

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

 

로컬 위임된 프로퍼티 (Local Delegated Properties)

로컬 변수도 위임될 수 있습니다:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

이는 계산 비용이 큰 값을 필요할 때만 초기화하는 데 유용합니다.

 

프로퍼티 델리게이트 요구사항 (Property Delegate Requirements)

프로퍼티 델리게이트는 특정 규약을 따라야 합니다. 읽기 전용 프로퍼티(val)의 경우 getValue() 함수를, 읽고 쓸 수 있는 프로퍼티(var)의 경우 추가로 setValue() 함수를 제공해야 합니다.

 

위임된 프로퍼티의 번역 규칙 (Translation Rules for Delegated Properties)

컴파일러는 위임된 프로퍼티를 위해 보조 프로퍼티를 생성합니다:

class C {
    var prop: Type by MyDelegate()
}

// 컴파일러가 생성하는 코드:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

 

델리게이트 제공 (Providing a Delegate)

provideDelegate 연산자를 정의하여 프로퍼티 델리게이트 인스턴스 생성 로직을 확장할 수 있습니다:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 델리게이트 생성
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

이 기능은 프로퍼티 초기화 시점에 추가적인 로직을 실행하고 싶을 때 유용합니다.

 

Kotlin의 위임된 프로퍼티는 코드의 재사용성과 유연성을 크게 향상시키는 강력한 기능입니다. 이를 통해 프로퍼티의 접근자 로직을 분리하고, 공통적인 패턴(예: 지연 초기화, 변경 감지)을 쉽게 구현할 수 있습니다.

 

주요 이점:

  1. 코드 재사용: 공통 로직을 델리게이트로 추출하여 재사용할 수 있습니다.
  2. 관심사의 분리: 프로퍼티의 접근과 저장 로직을 분리할 수 있습니다.
  3. 유연성: 런타임에 프로퍼티의 행동을 동적으로 변경할 수 있습니다.
  4. 표준 패턴: 지연 초기화, 관찰 가능한 프로퍼티 등의 패턴을 쉽게 구현할 수 있습니다.

그러나 위임된 프로퍼티를 과도하게 사용하면 코드의 복잡성이 증가할 수 있으므로, 적절한 상황에서 신중하게 사용해야 합니다. 특히 성능에 민감한 부분에서는 위임으로 인한 오버헤드를 고려해야 합니다.

위임된 프로퍼티는 특히 프레임워크나 라이브러리 개발, 복잡한 비즈니스 로직 구현, 설정 관리 등의 시나리오에서 그 진가를 발휘합니다. 이를 효과적으로 활용하면 더 유지보수가 쉽고 확장 가능한 코드를 작성할 수 있습니다.

 


Kotlin, 위임된프로퍼티, DelegatedProperties, 지연초기화, 관찰가능프로퍼티, 프로퍼티델리게이트, 코드재사용, 디자인패턴, 람다함수, 리플렉션, 안드로이드개발, 서버개발, 함수형프로그래밍, 성능최적화, 리팩토링

댓글