by란?
특정 처리를 다른 객체에게 넘기는 것(위임)을 의미합니다.
코틀린에서 by의 사용은 주로 두가지로 나누어 집니다.
- 프로퍼티 접근자(Property Accessors)의 구현을 다른 객체에 위임(Delegate)하는 것
- 인터페이스의 구현을 다른 개체에 위임하는 것
위임(delegation)이란?
- 위탁자(delegator) → 수탁자(delegate) 형태이며 어떤 일의 책임 및 처리를 다른 클래스 또는 메서드에게 넘긴다는 의미.
- 한 객체가 기능 일부를 다른 객체로 넘겨주어, 첫 번째 객체 대신 수행하도록 하는 일.
- 다른 클래스의 기능을 사용하되 그 기능을 변경하지 않으려면 상속 대신 위임.
- 위임을 활용하면 한 객체의 변경이 다른 객체에 미치는 영향이 작아짐.
위임(delegation)을 왜 사용할까?
객체지향 언어로 프로그램을 개발하다 보면 하위 클래스가 상위 클래스를 상속해서 상위 클래스의 메서드를 오버라이드를 하는 경우가 많습니다.
이런 오버 라이딩 상황에서 프로그램 유지보수를 하다 보면 상위 클래스가 내용이 변경이 되는 경우 하위 클래스가 상위 클래스에 의존 및 참조하고 있던 상황이 변경되면서 하위 클래스에 뜻하지 않은 에러가 발생합니다.
대표적인 상속의 문제점은 아래와 같습니다.
- 상위 클래스와 하위 클래스의 서로 간의 결합도가 높아져 상위 클래스의 변화가 하위 클래스에 주는 영향을 예측하기 어렵다.
- 불필요한 상위 클래스의 메서드까지 구현해야 한다.
- final클래스의 경우 상속이 불가능하다.
- 상속 구조가 복잡해질수록 그 영향에 대한 예측이 힘들어진다.
- 하위 클래스로 내려갈수록 기능이 더해져 파악하기 어렵다.
그래서 코틀린에서는 자바와는 달리 상속으로 인한 종속성, 의존성 문제를 방지하기 위해 기본적으로 클래스는 final 입니다.
만약 상속을 원한다면 상속될 클래스에 open 접근자를 명시해야 상위 클래스의 내용 변경 시 의존하고 있는 하위 클래스에 영향을 줄 수 있다는 것을 인지시켜 줄 수 있습니다.
이러한 종속성, 의존성 문제를 해결할 때 ☝️Delegation pattern(위임 패턴)은 상속의 좋은 대안으로 증명이 되었는데 Kotlin에서는 언어 자체에서 이러한 위임 패턴을 지원합니다.
Delegation pattern (위임 패턴)
: 소프트웨어 엔지니어링에서 위임 패턴은 특정 기능을 직접 구현하는 대신, 다른 객체에 그 기능을 위임하여 코드의 중복을 줄이고, 유연한 설계를 가능하게 하는 패턴입니다.
상속 vs 위임
코틀린에서는 클래스 상속보다는 by(위임)를 활용하는 것이 유리한 경우가 많습니다.
이는 조합(Composition)을 통한 코드 재사용 및 유연성을 높일 수 있기 때문입니다.
1) 상속보다 유연한 코드 작성 가능
상속은 단일 계층 구조이므로, 하나의 클래스만 상속받을 수 있습니다.
반면, 위임을 사용하면 여러 개의 클래스에서 기능을 조합할 수 있습니다.
// 상속 방식 (하나의 클래스만 상속 가능)
open class Parent {
fun parentFunction() = println("부모 기능")
}
class Child : Parent() {
fun childFunction() = println("자식 기능")
}
// 위임 방식 (여러 개의 클래스 조합 가능)
class ChildByDelegation(private val parent: Parent) {
fun childFunction() = println("자식 기능")
fun useParent() = parent.parentFunction() // 부모 기능을 위임받아 사용
}
위임을 사용하면 부모 클래스를 직접 상속하지 않고도 기능을 활용할 수 있어 유연성이 증가합니다.
2) 다중 상속 문제 해결
코틀린에서는 다중 상속을 지원하지 않지만, 위임을 활용하면 여러 개의 인터페이스 기능을 조합할 수 있습니다.
interface A {
fun doSomething() = println("A의 기능")
}
interface B {
fun doSomethingElse() = println("B의 기능")
}
// 위임을 활용하여 두 개의 인터페이스 기능을 결합
class MultiDelegation(a: A, b: B) : A by a, B by b
fun main() {
val obj = MultiDelegation(A {}, B {})
obj.doSomething() // "A의 기능"
obj.doSomethingElse() // "B의 기능"
}
상속을 사용했다면 불가능한 다중 인터페이스 조합이, 위임을 통해 가능해집니다.
3) 결합도(Dependency)를 낮출 수 있음
상속을 사용하면 부모 클래스의 변경이 자식 클래스에 영향을 미치지만, 위임을 사용하면 결합도를 낮출 수 있습니다.
class Parent {
fun function() = println("부모 기능")
}
// 부모 클래스를 직접 상속하는 경우 (결합도가 높음)
class Child : Parent()
// 위임을 사용하는 경우 (결합도가 낮음)
class ChildByDelegation(private val parent: Parent) {
fun useParent() = parent.function()
}
즉, 위임을 사용하면 Parent객체의 변경이 일어나도 ChildByDelegation에는 직접적인 영향이 없습니다.
따라서, 위임을 사용하면 부모-자식 간의 강한 결합에서 벗어나기 때문에 변경이 미치는 영향을 최소화 할 수 있습니다.
상속의 대안 "위임(Delegation)"
상속을 허용하지 않는 클래스에 새로운 기능을 추가할 때는 위임을 사용하며 사용 시 상속하지 않고 기존 기능을 그대로 사용하면서 새로운 기능을 추가할 수 있습니다.
즉, 쉽게 설명하면 하나의 클래스를 다른 클래스에 위임하도록 위임 선언을 하고 위임된 클래스가 가지는 인터페이스 메서드를 별도의 참조 없이 호출할 수 있도록 생성해주는 기능입니다.
이러한 위임을 코틀린에서는 "by" 키워드를 사용하여 위임 기능을 사용할 수 있습니다.
또한 코틀린은 중복 코드 없는 위임 패턴을 지원합니다.
1. 프로퍼티 접근자(Property Accessors)의 구현을 다른 객체에 위임(Delegate)
주로 안드로이드 개발을 하면서 흔히 사용되는 by lazy, by inject(), by mutableStateOf()등이 있습니다.
private val viewModel: SomeViewModel by lazy {
ViewModelProvider(requireActivity())[SomeViewModel::class.java]
}
private val viewModel: SomeViewModel by viewModels()
var someState by remember { mutableStateOf(false) }
위에서 사용되는 by를 이해하기 위해 간단한 예시를 하나 살펴보겠습니다.
변수를 선언하고 변수의 getter와 setter가 호출될 때 이에 대한 정보를 print하고 싶은 경우가 있다고 가정해 보겠습니다. 이 경우 아래와 같이 by를 이용하면 변수가 새로 선언될 때마다 변수들을 print할 수 있게 됩니다.
class ExampleUnitTest {
@Test
fun main() {
var intValue by PrintValueInfo(10)
intValue = 123
println(intValue)
}
}
class PrintValueInfo<T>(private var value: T) {
operator fun getValue(
thisRef: Any?,
property: KProperty<*>): T {
println("Get property name : ${property.name} thisRef : $thisRef value : $value")
return value
}
operator fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: T) {
println("Set property name : ${property.name} thisRef : $thisRef originValue :${this.value} newValue : $value")
this.value = value
}
}
[실행결과]
출력)
Set property name : intValue thisRef : null originValue :10 newValue : 123
Get property name : intValue thisRef : null value : 123
123
by PrintValueInfo(10)을 통해 value의 getter와 setter를 PrintValueInfo 클래스가 담당하게 됩니다.
value에 새로운 값을 할당할 때 setValue()가 실행되며, 값을 읽을 때 getValue()가 실행되게 됩니다.
위에 나열된 by lazy, by inject(), by mutableStateOf()등도 by에 의해 선언되면 지정되었던 작업이 이루어(provide by, delegate) 지기 때문에 by를 사용하는 것입니다.
2. 인터페이스의 구현을 다른 개체에 위임
class ExampleUnitTest {
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(b: Base) : Base by b { //Base의 구현을 b에 위임
override val message = "Message of Derived"
}
@Test
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
}
[실행결과]
출력)
BaseImpl: x = 10
Message of Derived
Derived는 BaseImpl의 기능을 직접 구현하지 않고, Base 인터페이스의 기능을 BaseImpl에 위임하여 사용합니다.
즉, Derived class에 구현되어 있지 않은 print() 함수를 Base 인터페이스를 구현한 BaseImpl로 부터 위임을 받아 사용이 가능하게 됩니다.
끝.
참고자료
https://kotlinlang.org/docs/reference/keyword-reference.html#soft-keywords
https://kotlinlang.org/docs/reference/keyword-reference.html#soft-keywords
kotlinlang.org
https://medium.com/mobile-app-development-publication/kotlin-by-made-simple-c6c08c1c16c4
Kotlin “by” Made Simple
Understand the “by” operator and keyword of Kotlin
medium.com
Kotlin의 클래스 위임은 어떻게 동작하는가
If you wanna read English version, please see here.
medium.com
코틀린 by란?
코틀린에서 by의 사용은 주로 두가지로 나누어 진다. 첫번째는 Property Accessors의 구현을 다른 객체에 위임하는 것이다. 주로 안드로이드 개발을 하면서 흔히 사용되는 by lazy, by inject(), by mutableState
leesmemo.tistory.com
https://velog.io/@jojo_devstory/%EC%BD%94%ED%8B%80%EB%A6%B0-Kotlin-by-by-the-way-what-is-this
코틀린 (Kotlin) by - by the way, what is this?
코틀린은 다양한 키워드 및 연산자를 지원하며 Hard Keywords, Soft Keywords, Modifier Keywords, Operators로 공식 코틀린 문서에 분류하여 소개되어있습니다.그중에서 Soft Keywords의 하나인 by에 대해 정리해보
velog.io
'Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린에서의 Null 처리 방법 (4) | 2024.11.05 |
---|---|
[Kotlin] 컬렉션(Collection) 함수 (9) | 2024.11.04 |
[Kotlin] open class와 abstract class (feat. 코틀린에서의 final) (0) | 2024.11.02 |
[Kotlin] 깊은 복사 (Deep Copy) 3가지 방법 (0) | 2024.11.02 |
[Kotlin] val vs Const val (0) | 2024.11.02 |