property

์ฝ”ํ‹€๋ฆฐ ํ”„๋กœํผํ‹ฐ ์œ„์ž„: by ํ‚ค์›Œ๋“œ๋กœ ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ ๋†’์ด๊ธฐ

์ฝ”ํ‹€๋ฆฐ์˜ **ํ”„๋กœํผํ‹ฐ ์œ„์ž„(Delegated Properties)**์€ ๊ฐ์ฒด์˜ ํŠน์ • ๋™์ž‘์„ ๋‹ค๋ฅธ ๊ฐ์ฒด์— ์œ„์ž„ํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” ํ”„๋กœํผํ‹ฐ ์œ„์ž„์ด ๋ฌด์—‡์ธ์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


ํ”„๋กœํผํ‹ฐ ์œ„์ž„์ด๋ž€?

ํ”„๋กœํผํ‹ฐ ์œ„์ž„์€ by ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ์˜ get()๊ณผ set() ์ ‘๊ทผ์ž ๋กœ์ง์„ ๋ณ„๋„์˜ ๊ฐ์ฒด(๋ธ๋ฆฌ๊ฒŒ์ดํŠธ)์— ๋งก๊ธฐ๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ฐœ๋ฐœ์ž๋Š” ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ์ง์ ‘ ๊ด€๋ฆฌํ•˜๋Š” ๋Œ€์‹ , get๊ณผ set์˜ ๋™์ž‘์„ ์ •์˜ํ•˜๋Š” ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๊ฐ์ฒด๋ฅผ ์ง€์ •ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๋ฌธ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

var <property name>: <Type> by <expression>

์—ฌ๊ธฐ์„œ <expression>์€ **getValue**์™€ setValue (var์˜ ๊ฒฝ์šฐ) ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์ฝ”ํ‹€๋ฆฐ์€ ์ด ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ž๋™์œผ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ์ฝ๊ณ  ์”๋‹ˆ๋‹ค.


ํ”„๋กœํผํ‹ฐ ์œ„์ž„์˜ ํ•ต์‹ฌ: ReadOnlyProperty์™€ ReadWriteProperty ์ธํ„ฐํŽ˜์ด์Šค

ํ”„๋กœํผํ‹ฐ ์œ„์ž„์— ์‚ฌ์šฉ๋˜๋Š” ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๊ฐ์ฒด๋Š” ํŠน์ • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ReadOnlyProperty: val (์ฝ๊ธฐ ์ „์šฉ) ํ”„๋กœํผํ‹ฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” getValue ๋ฉ”์„œ๋“œ๋งŒ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

    interface ReadOnlyProperty<in T, out V> {
        operator fun getValue(thisRef: T, property: KProperty<*>): V
    }
  • ReadWriteProperty: var (์ฝ๊ธฐ/์“ฐ๊ธฐ ๊ฐ€๋Šฅ) ํ”„๋กœํผํ‹ฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” **getValue**์™€ setValue ๋ฉ”์„œ๋“œ๋ฅผ ๋ชจ๋‘ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

    interface ReadWriteProperty<in T, out V> {
        operator fun getValue(thisRef: T, property: KProperty<*>): V
        operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
    }

์ฝ”ํ‹€๋ฆฐ ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์œ„์ž„๋œ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋งŒ๋‚ฌ์„ ๋•Œ, ๋‚ด๋ถ€์ ์œผ๋กœ ์ด ๋ฉ”์„œ๋“œ๋“ค์„ ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ๋กœ์ง์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.


์ฝ”ํ‹€๋ฆฐ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์œ ์šฉํ•œ ์œ„์ž„๋“ค

์ฝ”ํ‹€๋ฆฐ์€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์œ„์ž„ ํŒจํ„ด์„ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

1. lazy ์œ„์ž„: ์ง€์—ฐ ์ดˆ๊ธฐํ™”

**lazy**๋Š” ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์ด ์ฒ˜์Œ ์‚ฌ์šฉ๋  ๋•Œ ์ดˆ๊ธฐํ™”๋˜๋Š” ์ง€์—ฐ ์ดˆ๊ธฐํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ฆฌ์†Œ์Šค ์†Œ๋ชจ๊ฐ€ ํฐ ๊ฐ์ฒด๋ฅผ ํ•„์š”ํ•  ๋•Œ๋งŒ ์ƒ์„ฑํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

val expensiveProperty: String by lazy {
    println("๊ฐ’ ์ดˆ๊ธฐํ™” ์ค‘...")
    "์ดˆ๊ธฐํ™”๋œ ๊ฐ’"
}

fun main() {
    println(expensiveProperty) // "๊ฐ’ ์ดˆ๊ธฐํ™” ์ค‘..." ์ถœ๋ ฅ ํ›„ "์ดˆ๊ธฐํ™”๋œ ๊ฐ’" ์ถœ๋ ฅ
    println(expensiveProperty) // ์ด๋ฏธ ์ดˆ๊ธฐํ™”๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ๊ฐ’๋งŒ ์ถœ๋ ฅ
}

lazy๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฉฐ, LazyThreadSafetyMode๋ฅผ ํ†ตํ•ด ๋™์ž‘ ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. Delegates.observable์™€ Delegates.vetoable

์ด ์œ„์ž„๋“ค์€ ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ํŠน์ • ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋Š” ์˜ต์ €๋ฒ„ ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • Delegates.observable: ๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ํ›„์— ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

    import kotlin.properties.Delegates
    
    var name: String by Delegates.observable("์ดˆ๊ธฐ๊ฐ’") {
        prop, old, new ->
        println("์ด๋ฆ„์ด '$old'์—์„œ '$new'๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
    }
    
    fun main() {
        name = "์ฝ”ํ‹€๋ฆฐ"
        // ์ถœ๋ ฅ: ์ด๋ฆ„์ด '์ดˆ๊ธฐ๊ฐ’'์—์„œ '์ฝ”ํ‹€๋ฆฐ'๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    }
  • Delegates.vetoable: ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „์— ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•˜๋ฉฐ, ๋ณ€๊ฒฝ์„ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ์ด false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ฐ’ ๋ณ€๊ฒฝ์ด ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค.

    import kotlin.properties.Delegates
    
    var isEnabled: Boolean by Delegates.vetoable(false) {
        prop, old, new ->
        println("์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ๋„: $old -> $new")
        new // true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ๋งŒ ๊ฐ’์ด ๋ณ€๊ฒฝ๋จ
    }

3. Delegates.notNull

์ดˆ๊ธฐํ™”๊ฐ€ ๋‚˜์ค‘์— ์ด๋ฃจ์–ด์ง€์ง€๋งŒ null์ด ๋  ์ˆ˜ ์—†๋Š” var ํ”„๋กœํผํ‹ฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ดˆ๊ธฐํ™” ์ „์— ์ ‘๊ทผํ•˜๋ฉด IllegalStateException์„ ๋ฐœ์ƒ์‹œ์ผœ ์•ˆ์ „์„ฑ์„ ๋†’์—ฌ์ค๋‹ˆ๋‹ค.


์ปค์Šคํ…€ ํ”„๋กœํผํ‹ฐ ์œ„์ž„ ๋งŒ๋“ค๊ธฐ

ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์œ„์ž„ ์™ธ์—๋„, ์ง์ ‘ getValue์™€ setValue๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ปค์Šคํ…€ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ: ๋กœ์ปฌ ์ €์žฅ์†Œ์— ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ์œ„์ž„

import kotlin.reflect.KProperty

class LocalStorageDelegate(private val key: String, private val defaultValue: String) {
    // SharedPreferences ๋“ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •
    private val storage = mutableMapOf<String, String>()

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("'$key'์—์„œ ๊ฐ’ ์ฝ๊ธฐ")
        return storage[key] ?: defaultValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("'$key'์— '$value' ์“ฐ๊ธฐ")
        storage[key] = value
    }
}

class UserSettings {
    var username: String by LocalStorageDelegate("username", "Guest")
}

fun main() {
    val settings = UserSettings()
    println(settings.username) // ์ถœ๋ ฅ: 'username'์—์„œ ๊ฐ’ ์ฝ๊ธฐ, Guest
    settings.username = "Alice" // ์ถœ๋ ฅ: 'username'์— 'Alice' ์“ฐ๊ธฐ
    println(settings.username) // ์ถœ๋ ฅ: 'username'์—์„œ ๊ฐ’ ์ฝ๊ธฐ, Alice
}

์ด์ฒ˜๋Ÿผ ํ”„๋กœํผํ‹ฐ ์œ„์ž„์„ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๊น…, ๋ฐ์ดํ„ฐ ์ €์žฅ, ์Šค๋ ˆ๋“œ ๊ด€๋ฆฌ ๋“ฑ ๋‹ค์–‘ํ•œ ๋กœ์ง์„ ํ•œ ๊ณณ์— ๋ชจ์•„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Last updated