질문 정리

위임하는 방식(By)과 직접 할당하는 방식(=)의 차이는 무엇일까?

five2week 2024. 5. 24. 09:08

현재 프로젝트의 마감일이 가까워짐에 따라 코드를 점검하는 과정에서 다음의 방식을 혼용하고 있음을 알게 되었습니다.

val a = getSurveyViewModel.a.collectAsState().value
val a by getSurveyViewModel.a.collectAsState()

val a by viewModel.a.collectAsState()

  • 이 구문에서 by 키워드는 위임(delegate)을 의미합니다.
  • collectAsState()는 Flow를 State로 변환하는 함수입니다. 이 함수는 Compose에서 상태(State)를 구독(subscribe)하고 UI를 다시 렌더링할 수 있게 해줍니다.
  • by 키워드를 사용하면 State 객체를 a 변수에 위임하여, a 를 직접 State 객체처럼 사용할 수 있습니다.
  • 해당 방식을 사용하면 State 객체의 value 속성을 매번 호출할 필요 없이 바로 값을 사용할 수 있습니다.

val a = viewModel.a.collectAsState().value

  • 이 구문에서는 collectAsState()를 호출하여 Flow를 State로 변환한 후, 그 State 객체의 value 속성에 접근하여 실제 값을 a 변수에 할당합니다.
  • a 변수는 State 객체가 아닌 실제 값 자체를 가지고 있게 됩니다.
  • 이 경우 a는 Compose 상태 변경에 따른 자동 갱신이 이루어지지 않습니다. 다시 말해, a 변수는 Flow에서 새 값이 방출될 때마다 자동으로 업데이트되지 않습니다.

장점

  1. 자동 UI 갱신:
    • 전자의 방식은 Flow의 상태 변화에 따라 자동으로 UI를 갱신합니다. 이는 Compose의 선언적 UI 패턴에 적합합니다.
  2. 간결함:
    • by 키워드를 사용하면 State 객체의 value 속성을 반복해서 호출할 필요가 없어 코드가 간결해집니다.

고려 사항

  1. 필요한 경우:
    • UI 갱신이 필요하지 않은 경우(예: 값이 초기화될 때만 필요하거나, 특정한 시점에만 필요할 때)에는 후자의 방식을 사용할 수도 있습니다. 그러나 이러한 경우는 드물며 특별한 이유가 없는 한 전자의 방식으로 통일하는 것이 좋습니다.
  2. 성능:
    • 전자의 방식은 Flow의 모든 상태 변화를 감지하여 UI를 갱신하므로, 상태 변화가 빈번하게 일어나는 경우 성능에 영향을 미칠 수 있습니다. 이런 경우에는 상태 변화 빈도를 조절하거나 필요한 최소한의 갱신만 일어나도록 설계할 필요가 있습니다.

결론

프로젝트 전체에서 상태 관리 방식을 전자의 방식(by 키워드를 사용하는 방식)으로 통일하는 것이 더 좋을 것 같다는 결론을 내렸습니다. 모든 경우에 해당 방식이 필요한 것은 아니고, UI 갱신이 필요하지 않은 경우에 주로 두 번째 방식을 사용하고 있었기 때문에 코드를 꼭 변경해야 하는 것은 아니었지만, 코드의 일관성과 가독성을 위해서 통일하게 되었습니다.

 

위임 패턴(Delegation Pattern)

위임 패턴은 객체 지향 프로그래밍에서 널리 사용되는 디자인 패턴 중 하나로, 특정 작업이나 행동을 다른 객체(위임자)에게 맡기는 패턴을 말합니다. 이 패턴의 핵심 목적은 책임과 기능을 분산시켜 코드의 복잡성을 줄이고, 유연성과 재사용성을 향상시키는 데 있습니다.

 

위임 패턴은 일반적으로 두 개의 객체를 포함합니다.

  1. Client (클라이언트): 작업을 요청하는 객체입니다.
  2. Delegate (위임자): 실제 작업을 수행하는 객체입니다.

위의 예제를 적용하면 다음과 같이 설명할 수 있습니다.

  • Client(클라이언트): a 변수는 값을 필요로 할 때 위임자에게 요청을 보냅니다.
  • Delegate(위임자): viewModel.a.collectAsState()로 생성된 State 객체는 a 변수의 요청을 처리하고, 필요한 값을 제공합니다.

클라이언트는 특정 작업을 위임자 객체에 위임하고, 위임자는 그 작업을 수행한 결과를 클라이언트에게 반환합니다. 이렇게 함으로써 클라이언트는 자신의 책임 범위 내에서 작업을 관리할 수 있으며, 세부적인 구현에 대해서는 신경 쓸 필요가 없습니다.

장점

  • 코드의 간결성: 위임을 사용함으로써 반복되는 코드를 줄이고, 코드의 가독성을 향상시킬 수 있습니다.
  • 재사용성 및 유연성: 다른 객체의 기능을 재사용함으로써, 코드 수정 없이도 객체의 행동을 변경하거나 확장할 수 있습니다.
  • 책임의 분리: 각 객체가 자신의 책임에만 집중할 수 있게 되므로, 시스템의 전체적인 설계가 명확해집니다.

Kotlin에서의 위임

Kotlin 프로그래밍 언어에서는 by 키워드를 사용하여 속성 위임을 간단히 구현할 수 있습니다. 이는 변수의 값을 다른 객체가 관리하도록 하여, 해당 변수에 접근할 때마다 자동으로 위임자의 메소드를 호출하게 됩니다.

val a by viewModel.a.collectAsState()

 

이 예에서 a 변수는 collectAsState() 함수로부터 반환된 State 객체에 위임되어, a를 통해 State 객체의 value 속성에 직접 접근할 수 있게 됩니다. 이로 인해 a 변수는 마치 State 객체인 것처럼 동작하며, State의 value를 직접 참조하는 것과 같은 효과를 낼 수 있습니다.

 

하지만 내부구조가 궁금하다면, 다음과 같이 표현할 수 있습니다. 

// 1
interface State<T> {
    val value: T
}

// 2
class MyViewModel {
    val a: State<String> = object : State<String> {
        override val value: String
            get() = "Sample State Value"
    }
}

// 3
class MyActivity {
    private val viewModel = SurveyViewModel()

    val a: String
        get() = viewModel.a.value
}

fun main() {
    val activity = MyActivity()
    println(activity.a)  // "Sample State Value"를 출력
}

 

1. State <T>란 무엇일까 ?

여기서 State<T>는 Kotlin에서 사용되는 제네릭 인터페이스입니다. 이 인터페이스는 어떤 타입 T의 값을 갖는 value 속성을 정의합니다. State 인터페이스는 상태 관리 패턴에서 중요한 역할을 하며, 특정 상태의 현재 값을 나타내는 데 사용됩니다.

 

2. State를 사용해서 a를 만들면 String a를 만들어서 "Sample State Value"라고 넣어주는 것과 뭐가 다를까?

위의 차이는 어플리케이션의 상태관리와 UI 갱신에 있어서 차이가 있습니다. State 객체를 사용하면 상태 반응성이라는 것이 생깁니다. 이것은 State의 객체 값이 변경되면, 이 변경을 감지하고, 그에 따라서 UI를 자동으로 갱신할 수 있습니다. 이는 데이터 바인딩과 상태 동기화를 통해서 이루어집니다.

 

3. a는 a의 값을 위임 받아서 그냥 직접할당(=) 과 다르게 a를 호출할 때 마다 viewModel.a.value의 값을 조회하여 그 값을 반환합니다.