본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Computer Science

위임 패턴과 Kotlin by

작성 2026. 10. 14.추가
목차6개 항목
출처와 참고

팔만코딩경(42Seoul 커뮤니티 위키)의 「[Kotlin] by 키워드」를 바탕으로 재구성한 노트다.

위임 패턴이란

위임(delegation)은 객체가 받은 요청을 직접 처리하지 않고 가지고 있는 다른 객체(helper)에게 넘기는 방식이다. 상속이 "부모의 구현을 내 것으로 물려받는" 것이라면, 위임은 "구현을 가진 객체를 품고 일을 맡기는" 것이다.

class Rectangle(val width: Int, val height: Int) {
    fun area() = width * height
}
 
class Window(val bounds: Rectangle) {
    fun area() = bounds.area()   // 요청을 bounds에 넘긴다
}
비교상속위임(합성)
관계is-ahas-a
결합부모 구현 세부에 강하게 묶인다인터페이스로만 의존한다
교체컴파일 시점에 고정실행 중에 다른 객체로 바꿀 수 있다
단점부모 변경이 자식을 깨뜨린다넘겨주는 메서드를 일일이 써야 한다

"상속보다 합성"이 권장되는 이유가 왼쪽 열의 결합 문제다. 오른쪽 열의 단점, 즉 위임용 보일러플레이트를 Kotlin은 by로 없앤다.

클래스 위임

인터페이스 by 객체라고 쓰면 컴파일러가 그 인터페이스의 모든 메서드를 해당 객체로 넘기는 코드를 생성한다.

interface ClosedShape { fun area(): Int }
class Rectangle(val w: Int, val h: Int) : ClosedShape { override fun area() = w * h }
class Circle(val r: Int) : ClosedShape { override fun area() = r * r * 3 }
 
class Window(private val bounds: ClosedShape) : ClosedShape by bounds
 
Window(Circle(5)).area()        // 75
Window(Rectangle(4, 5)).area()  // 20
  • Window는 ClosedShape를 구현하지만 area()를 직접 쓰지 않는다.
  • 필요한 메서드만 override하면 그 메서드는 위임 대신 직접 구현이 쓰인다. 데코레이터 패턴(일부 동작만 감싸기)이 몇 줄로 끝난다.
  • 위임 대상은 인터페이스여야 한다. 클래스 자체를 by로 위임할 수는 없다.

프로퍼티 위임

val/var 이름: 타입 by 위임객체는 프로퍼티의 접근자(getter/setter)를 위임 객체의 getValue/setValue 연산자에 맡긴다.

import kotlin.reflect.KProperty
 
class Logged {
    private var value = ""
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("${property.name}: $value -> $newValue")
        value = newValue
    }
}
 
class User { var name: String by Logged() }
 
val u = User()
u.name = "Kim"   // name:  -> Kim 출력

u.name을 읽으면 getValue가, 쓰면 setValue가 불린다. 값 검증, 변경 로그, 지연 계산처럼 여러 프로퍼티에 반복되는 접근 로직을 한 클래스로 뽑아낼 수 있다.

표준 위임

위임동작
by lazy { ... }처음 읽을 때 람다를 실행해 값을 만들고, 이후에는 저장된 값을 돌려준다. 기본은 스레드 안전(synchronized). lateinit과의 차이는 Kotlin 핵심 문법
Delegates.observable(초깃값) { ... }값이 바뀔 때마다 콜백 호출
Delegates.vetoable(초깃값) { ... }콜백이 false를 돌려주면 변경을 거부
by map프로퍼티를 맵의 같은 이름 키에서 읽는다 (JSON 같은 동적 데이터)
by ::other다른 프로퍼티로 넘긴다 (이름 변경 시 하위 호환)
val config: Config by lazy {
    println("computed!")
    loadConfig()          // 첫 접근에서 한 번만 실행
}

Android의 by viewModels()

class MainActivity : ComponentActivity() {
    private val viewModel: MainViewModel by viewModels()
}

viewModels()는 Lazy<VM>을 돌려주는 함수다. 내부 구현은 by lazy와 같은 원리로, 처음 접근할 때 ViewModelProvider에서 ViewModel을 얻어 캐시하고 이후에는 캐시를 돌려준다. 그래서 Activity가 화면 회전으로 다시 만들어져도 같은 ViewModel 인스턴스를 받는다. Fragment에서는 by viewModels()(Fragment 범위)와 by activityViewModels()(Activity 범위 공유)를 구분해 쓴다.

정리

  • 위임은 구현을 상속받지 않고 다른 객체에 일을 맡기는 방식이며, 상속보다 결합이 느슨하다.
  • Kotlin의 클래스 위임(: 인터페이스 by 객체)은 넘겨주기 코드를 컴파일러가 대신 생성한다.
  • 프로퍼티 위임은 getter/setter를 getValue/setValue를 가진 객체에 맡긴다.
  • by lazy, Delegates.observable, by viewModels()가 모두 프로퍼티 위임이다.

관련 노트: Kotlin 핵심 문법, 추상 클래스와 인터페이스, JavaScript 디자인 패턴: 생성 패턴, 모바일 앱 프레임워크 비교

원본 기록3

댓글

GitHub 계정으로 댓글을 남길 수 있어요.