2022-03-15 작성완료
코틀린은 네이티브 레벨 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()
}
SoundBehavior
인터페이스를 정의한다.ScreamBehavior
클래스와 RockAndRollBehavior
클래스는 해당 인터페이스를 구현하고 메서드에 대한 자신만의 구현을 가진다.TomAraya
클래스와 ElvisPresley
클래스도 해당 인터페이스를 구현하지만 메서드를 구현하지는 않는다. 대신에, 그것들은 메서드 호출을 책임이 있는 구현에게(responsible implementation) 위임한다. delegate object는 by
키워드 뒤에 정의된다. 보다시피, 보일러플레이트 코드가 필요하지 않다. makeSound()
가 TomAraya
타입의 tomAraya
에서, 또는 ElvisPresley
타입의 elvisPresley
에서 호출될 때, 그 호출은 상응하는 delegate object로 위임된다.코틀린은 프로퍼티 위임 메커니즘을 제공한다. 이는 프로퍼티 set
과 get
메서드 호출을 특정 객체에 위임하는 것을 허용한다. 이 경우 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"
}
String
타입의 p
프로퍼티를 클래스 Delegate
의 인스턴스에 위임한다. delegate object는 by
키워드 뒤에서 정의된다.getValue
만 필요하다. 코틀린 표준 라이브러리는 유용한 위임들을 포함하고 있다. 예: 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
}
lazy
는 객체 생성 시 초기화되지 않는다. get()
호출은 lazy()
에 아규먼트로 전달된 람다 표현식을 실행시키고 그 결과를 저장한다.get()
호출은 저장된 값을 반환한다. 스레드 세이프티를 원한다면 대신 blockingLazy()
를 사용해아 한다: 이것은 값이 한 스레드에서만 계산되며 모든 스레드가 같은 값을 바라본다는 것을 보장한다.
프로퍼티 위임은 맵에 프로퍼티를 저장하기 위해 사용될 수도 있다. 이것은 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}")
}
변경 가능한 속성을 맵에 위임할 수도 있다. 이 경우 맵은 프로퍼티 할당 시 수정될 것이다. 읽기 전용 Map
대신 MutableMap
를 사용해야 한다.