안드로이드 코드를 짜면서 by lazy
, by viewModels()
를 쓸 때 by
라는 키워드를 사용했는데 이것이 어떤 역할을 하는지 작성해보려고 한다.
객체가 요청을 다른 객체(helper object)에 위임해서 처리하는 패턴이다.
class Rectangle(val width: Int, val height: Int) {
fun area() = width * height
}
class Window(val bounds: Rectangle) {
//위임
fun area() = bounds.area()
}
여기서 Window
클래스는 area
메서드로 호출되는 요청을 인자로 갖고 있는 Rectangle
객체에 위임하고 있다.
interface ClosedShape {
fun area(): Int
}
class Rectangle(val width: Int, val height: Int) : ClosedShape {
override fun area() = width * height
}
class Circle(val radius: Int) : ClosedShape {
override fun area() = radius * radius * 3
}
class Window(private val bounds: ClosedShape) : ClosedShape by bounds
fun main() {
val circleWindow = Window(Circle(5))
val rectangleWindow = Window(Rectangle(4, 5))
println(circleWindow.area()) //prints 75
println(rectangleWindow.area()) //prints 20
}
코틀린에서는 by
키워드를 사용하여 언어 차원에서 위임 패턴을 구현할 수 있다. Window
클래스는 ClosedShape
를 구현하며, by
키워드로 인해 인자로 전달된 bounds
가 구현한 override
메서드들을 사용할 수 있게 된다.
import kotlin.reflect.KProperty
class Example {
var p: String by Delegate()
}
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.")
}
}
프로퍼티 위임은 프로퍼티의 Accessor(get
, set
)을 delegate에 위임하는 것이다. 위의 코드에서 프로퍼티p:String
은 Delegate
에 getValue
와 setValue
를 이임하고, Delegate
클래스 내에 각각 오버로드돼있다.
val example = Example()
println(example.p) // prints Example@33a17727, thank you for delegating 'p' to me!
example.p = "NEW" // prints NEW has been assigned to 'p' in Example@33a17727.
p
를 읽으면 get
이 Delegate
에 위임돼있으므로 getValue
메서드가 호출된다. p
를 수정하면 set
이 Delegate
에 위임돼있으므로 setValue
메서드가 호출된다. 각각 내부의 String
이 리턴돼 위 코드의 주석처럼 출력된다.
lazy()
메서드는 람다를 인자로 받아 Lazy<T>
인스턴스를 반환한다. get()
의 첫 호출은 lazy()
에 전달된 람다를 기억하고 그 후의 get()
호출은 기억한 값을 그대로 반환한다.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
// computed!
// Hello
// Hello
첫 println(lazyValue)
는 lazy
에 전달된 람다를 호출하여 computed
가 같이 출력되고 "Hello"
를 저장하고, 다음 println(lazyValue)
는 저장된 값을 그대로 반환하여 "Hello"
만 반환한다.
val someViewModel: SomeViewModel by viewModels()
프로퍼티 someViewModel
은 SomeViewModel
타입이며, Accessor를 viewModels()
에 위임한다.