Kotlin Examples > Delegation

HH·2022년 3월 16일
0

Kotlin Examples

목록 보기
6/8

2022-03-15 작성완료

Delegation Pattern


코틀린은 네이티브 레벨 Delegation Pattern의 쉬운 구현을 지원한다. 여기에는 보일러플레이트 코드가 사용되지 않는다.

interface SoundBehavior {                                                          // 1
    fun makeSound()
}

class ScreamBehavior(val n:String): SoundBehavior {                                // 2
    override fun makeSound() = println("${n.toUpperCase()} !!!")
}

class RockAndRollBehavior(val n:String): SoundBehavior {                           // 2
    override fun makeSound() = println("I'm The King of Rock 'N' Roll: $n")
}

// Tom Araya is the "singer" of Slayer
class TomAraya(n:String): SoundBehavior by ScreamBehavior(n)                       // 3

// You should know ;)
class ElvisPresley(n:String): SoundBehavior by RockAndRollBehavior(n)              // 3

fun main() {
    val tomAraya = TomAraya("Thrash Metal")
    tomAraya.makeSound()                                                           // 4
    val elvisPresley = ElvisPresley("Dancin' to the Jailhouse Rock.")
    elvisPresley.makeSound()
}
  1. 메서드 하나를 가진 SoundBehavior 인터페이스를 정의한다.
  2. ScreamBehavior 클래스와 RockAndRollBehavior 클래스는 해당 인터페이스를 구현하고 메서드에 대한 자신만의 구현을 가진다.
  3. TomAraya 클래스와 ElvisPresley클래스도 해당 인터페이스를 구현하지만 메서드를 구현하지는 않는다. 대신에, 그것들은 메서드 호출을 책임이 있는 구현에게(responsible implementation) 위임한다. delegate object는 by 키워드 뒤에 정의된다. 보다시피, 보일러플레이트 코드가 필요하지 않다.
  4. makeSound()TomAraya타입의 tomAraya에서, 또는 ElvisPresley 타입의 elvisPresley에서 호출될 때, 그 호출은 상응하는 delegate object로 위임된다.

Delegated Properties


코틀린은 프로퍼티 위임 메커니즘을 제공한다. 이는 프로퍼티 setget 메서드 호출을 특정 객체에 위임하는 것을 허용한다. 이 경우 delegate object는 반드시 getValue 메서드를 갖고 있어야 한다. 가변 프로퍼티의 경우 setValue또한 필요하다.

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()                                               // 1

    override fun toString() = "Example Class"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {        // 2     
        return "$thisRef, thank you for delegating '${prop.name}' to me!"
    }

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

fun main() {
    val e = Example()
    println(e.p)
    e.p = "NEW"
}
  1. String 타입의 p 프로퍼티를 클래스 Delegate의 인스턴스에 위임한다. delegate object는 by 키워드 뒤에서 정의된다.
  2. 메서드 위임. 이 메서드들의 시그니처는 언제나 예제에서 보여주는 것과 같다. 구현은 필요한 어떤 단계이든 포함할 수 있을 것이다. 불변 프로퍼티에는 getValue만 필요하다.

Standard Delegates


코틀린 표준 라이브러리는 유용한 위임들을 포함하고 있다. 예: lazy, observable 등. 이것들을 그대로 사용할 수 있다. 예를 들어 lazy는 lazy initialization에 사용된다.

class LazySample {
    init {
      println("created!")            // 1
    }
    
    val lazyStr: String by lazy {
        println("computed!")          // 2
        "my lazy"
    }
}

fun main() {
    val sample = LazySample()         // 1
    println("lazyStr = ${sample.lazyStr}")  // 2
    println(" = ${sample.lazyStr}")  // 3
}
  1. 프로퍼티 lazy는 객체 생성 시 초기화되지 않는다.
  2. get() 호출은 lazy()에 아규먼트로 전달된 람다 표현식을 실행시키고 그 결과를 저장한다.
  3. 이후 get() 호출은 저장된 값을 반환한다.

스레드 세이프티를 원한다면 대신 blockingLazy()를 사용해아 한다: 이것은 값이 한 스레드에서만 계산되며 모든 스레드가 같은 값을 바라본다는 것을 보장한다.


Storing Properties in a Map


프로퍼티 위임은 맵에 프로퍼티를 저장하기 위해 사용될 수도 있다. 이것은 JSON 파싱 또는 다른 동적 작업에 유용하다.

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

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

    println("name = ${user.name}, age = ${user.age}")
}
  1. 대리자(Delegates)는 프로퍼티 이름인 문자열 키를 사용해 맵으로부터 값을 가져온다.

변경 가능한 속성을 맵에 위임할 수도 있다. 이 경우 맵은 프로퍼티 할당 시 수정될 것이다. 읽기 전용 Map 대신 MutableMap를 사용해야 한다.

0개의 댓글