안녕세계
[Kotlin] Delegation (위임) 본문
[Kotlin] Delegation (위임)
Junhong Kim 2023. 6. 18. 23:03Delegation 이란?
Delegation(위임)은 클래스나 프로퍼티의 기능을 다른 객체에 위임하는 디자인 패턴입니다. Kotlin에서는 보일러 플레이트 코드 없이 `by` 키워드를 사용해서 `Delegation Pattern`을 구현할 수 있는 방법을 지원합니다.
클래스 위임
클래스 위임은 특정 인스턴스의 메서드 호출시 해당 기능을 다른 클래스에게 위임합니다. 다음 예제는 `Child` 클래스의 `pay`를 호출하면 `Parent` 클래스의 `pay` 메서드를 실행합니다. 이때 `Child` 클래스 선언에 `by` 키워드를 사용했으며, `Parent` 클래스의 인스턴스를 생성자 파라미터로 전달합니다. 이제 `Child` 클래스가 `pay` 기능을 사용하면 `Parent` 클래스에게 위임하여 `pay` 메서드를 호출합니다.
일반적인 클래스 위임
interface Adult {
fun pay()
}
class Parent : Adult {
override fun pay() {
println("Paying bills by parent")
}
}
class Child(private val parent: Parent) : Adult {
override fun pay() {
parent.pay() // parent의 pay 기능을 사용한다
}
}
fun main() {
val parent = Parent()
val child = Child(parent)
child.pay()
}
---
Paying bills by parent
`by`를 사용한 클래스 위임
interface Adult {
fun pay()
}
class Parent : Adult {
override fun pay() {
println("Paying bills by parent")
}
}
class Child(private val parent: Parent) : Adult by parent // Adult 기능을 모두 Parent에게 위임한다.
// [참고] 다음과 같은 방식으로도 위임할 수 있다.
// class Child : Adult by Parent()
fun main() {
val parent = Parent()
val child = Child(parent)
child.pay()
}
---
Paying bills by parent
Delegated Properties (위임 프로퍼티)
앞서 살펴본 `Delegation Pattern`을 프로퍼티에 적용해서 접근자(accessor) 기능을 위임 객체가 수행할 수 있도록 위임하는 방법을 알아봅니다. 위임 프로퍼티의 문법은 다음과 같으며, `p` 프로퍼티는 접근자 로직을 다른 객체에 위임한다는 것을 의미합니다.
class Example {
var p: String by Delegate()
}
프로퍼티 위임 관례를 따르기 위해서는 위임 클래스(Delegate)에 `getValue`와 `setValue` 메서드를 제공해야 합니다. 이때, `operator` 키워드를 사용하여 getter/setter 로직을 구현한다는 것에 유의해주세요.
class Delegate {
// getter
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
// setter
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
Delegate 구현에 대한 내용을 확인했으니 Example 클래스의 접근자 기능을 사용해보면, 다음과 같이 Delegate의 접근자 로직이 수행된 것을 확인할 수 있습니다.
fun main() {
val e = Example()
println(e.p)
e.p = "NEW"
}
---
delegate.Example@614c5515, thank you for delegating 'p' to me!
NEW has been assigned to 'p' in delegate.Example@614c5515.
Standard delegates
Lazy Properties (지연 초기화)
지연 초기화는 객체의 일부분을 초기화하지 않고 실제 그 값이 필요할 때 초기화하는 패턴입니다. 초기화 과정에 많은 자원을 사용하거나 객체를 사용할 때마다 초기화하지 않아도 되는 프로퍼티에 대해 지연 초기화 패턴을 사용할 수 있습니다. 예를 들어, 특정 프로퍼티를 초기화하기 위해서 많은 연산 또는 시간이 오래 걸릴 경우 초기화를 뒤로 미룰 수 있습니다.
`backing property`를 사용한 지연 초기화 구현
class Person(val name: String) {
private var _emails: List<String>? = null
val emails: List<String>
get() {
if (_emails == null) {
_emails = loadEmails(this) // 최초 접근시 이메일 load
}
return _emails!!
}
}
fun loadEmails(person: Person): List<String> {
println("Load emails for ${person.name}")
return listOf("email1", "email2")
}
fun main() {
val person = Person("Kim")
println(person.emails)
println(person.emails)
}
---
Load emails for Kim
[email1, email2]
[email1, email2]
위임 프로퍼티를 사용한 지연 초기화 구현
`backing property`를 사용한 지연 초기화는 많은 코드를 작성해야하고 스레드 세이프하지 않습니다. 코틀린에서는 위임 프로퍼티를 사용해서 코드를 간단하게 할 수 있으며 getter 로직을 캡슐화 해줍니다. 위임 객체를 반환하는 표준 라이브러리 `lazy` 함수는 getValue 메서드가 있는 객체를 반환합니다. 따라서, `lazy`를 `by` 키워드와 함꼐 사용해서 위임 프로퍼티를 만들 수 있습니다.
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
fun loadEmails(person: Person): List<String> {
println("Load emails for ${person.name}")
return listOf("email1", "email2")
}
fun main() {
val person = Person("Kim")
println(person.emails)
println(person.emails)
}
---
Load emails for Kim
[email1, email2]
[email1, email2]
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"
}
---
<no name> -> first
first -> second
만약, 할당을 가로채서 거부하려면 `observable()` 대신 `vetoable()`을 사용하면 됩니다. vetoable에 전달된 핸들러는 새 프로퍼티 값을 할당하기 전에 호출됩니다.
참고
https://kotlinlang.org/docs/delegation.html
https://in-kotlin.com/design-patterns/interface-and-property-delegation/#Override_Interface_members
'Language > Kotlin' 카테고리의 다른 글
[Kotlin] 아키텍처 테스트 (feat. ArchUnit) (4) | 2024.10.31 |
---|---|
[Kotlin] 제네릭과 무공변 (feat. 공변, 반공변) (1) | 2024.03.31 |
[Kotlin] Higher-order Functions (고차 함수) (0) | 2023.07.02 |
[Kotlin] Extension Functions (확장 함수) (0) | 2023.06.04 |
[Kotlin] Scope Functions 차이점 (2) | 2023.05.21 |