[Kotlin/코틀린] MVVM 패턴이란? - MVVM 패턴과 Counter 앱

반응형
SMALL

안드로이드 앱 개발에서 널리 사용되는 아키텍처 패턴 중 하나인 MVVM에 대해서 기록하려고 한다.

요즘 안드로이드 개발을 취미로 시작하여 기록을 위해 작성한다. 혹여나 틀린 정보가 있다면 댓글로 남겨주신다면 반영하겠습니다 감사합니다.

 

서론

MVVM 패턴이란?

MVVM (Model-View-ViewModel) 패턴은 앱의 UI와 비즈니스 로직을 분리하여 보다 깔끔한 구조와 유지보수성을 제공하는 아키텍처 패턴이다. MVVM은 크게 세 가지 컴포넌트로 구성된다.

  • Model: 데이터와 비즈니스 로직을 관리하는 컴포넌트
  • View: 사용자에게 보이는 UI
  • ViewModel: Model과 View 사이의 중간 역할을 하며, 데이터를 가공하여 View에 전달하고 사용자 인터렉션을 처리

MVVM은 데이터를 UI에 쉽게 바인딩하고, 코드가 모듈화 되어 있어 재사용성과 테스트 용이성을 높이는 데 도움이 된다.

 

본론

MVVM은 왜 사용할까?

MVVM 패턴을 사용하면 UI와 비즈니스 로직을 명확하게 분리할 수 있다. 이는 특히 앱이 복잡해질수록 개발 및 유지보수에 유리하다.

ViewModel을 활용하면 UI 상태를 관리하고 비즈니스 로직을 처리하는 코드가 분리되어 코드 가독성이 향상되고 테스트가 용이하다.

 

장점

  • UI와 비즈니스 로직의 분리: 코드가 더 구조적이고 이해하기 쉽다.
  • 재사용성과 테스트 용이성: ViewModel은 앱의 비즈니스 로직과 데이터를 독립적으로 관리하므로, ViewModel을 별도로 테스트하거나 재사용할 수 있다.
  • 데이터 바인딩 지원: ViewModel을 통해 UI와 데이터를 쉽게 바인딩할 수 있어 UI 업데이트가 용이

단점

  • 복잡도 증가: 작은 프로젝트에서는 MVVM을 사용하면 오히려 코드가 복잡해질 수 있다.
  • 초기 학습 곡선: ViewModel, LiveData 등의 개념을 이해하고 적용하기 위해 다소 높은 학습 곡선이 있다.

 

예시: Counter 앱에서 MVVM 적용하기

MainActivity - View 초기화 및 ViewModel 구성

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            // CounterViewModel 인스턴스 생성
            val viewModel: CounterViewModel = viewModel()

            // 테마 설정 및 전체 레이아웃 설정
            Counter_Theme {
                // 배경색을 테마에 따라 설정
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    // Counter 화면을 ViewModel과 함께 호출
                    TheCounterApp(viewModel)
                }
            }
        }
    }
}
  • viewModel 함수로 CounterViewModel 인스턴스를 생성해 UI와 연결
  • TheCounterApp @Composable에 viewModel을 전달하여 View와 ViewModel을 연결

TheCounterApp - UI

data class CounterModel(var count: Int)

class CounterRepository {
    private var _counter = CounterModel(0)

    // 현재 카운터 값 반환
    fun getCounter() = _counter

    // 카운터 증가
    fun incrementCounter() {
        _counter.count++
    }

    // 카운터 감소
    fun decrementCounter() {
        _counter.count--
    }
}

 

  • CounterModel은 카운터 값을 저장하는 데이터 클래스
  • CounterRepository는 CounterModel의 인스턴스를 관리하고, 카운터 값을 조회하거나 변경하는 메서드를 제공
  • incrementCounter()와 decrementCounter() 메서드를 통해 카운터 값을 변경

 

TheCounterApp - UI

@Composable
fun TheCounterApp(viewModel: CounterViewModel) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 현재 카운트 값을 표시하는 Text
        Text(
            text = "Count : ${viewModel.count.value}",
            fontSize = 24.sp,
            fontWeight = FontWeight.Bold
        )

        Spacer(modifier = Modifier.height(16.dp))

        // 증가와 감소 버튼을 포함한 Row 레이아웃
        Row {
            Button(onClick = { viewModel.increment() }) {
                Text(text = "increment")
            }
            Button(onClick = { viewModel.decrement() }) {
                Text(text = "decrement")
            }
        }
    }
}

 

  • Column과 Row 레이아웃을 사용하여 UI를 구성
  • Text로 ViewModel에서 가져온 count 값을 표시하고, increment와 decrement 버튼을 추가하여 값을 변경
  • viewModel.increment()와 viewModel.decrement() 함수 호출을 통해 ViewModel의 데이터를 변경

CounterViewModel - ViewModel

class CounterViewModel : ViewModel() {
    private val _repository: CounterRepository = CounterRepository()
    private val _count = mutableStateOf(_repository.getCounter().count)

    // 외부에서 읽기만 가능한 count 상태
    val count: MutableState<Int> = _count

    // 증가 함수
    fun increment() {
        _repository.incrementCounter()
        _count.value = _repository.getCounter().count
    }

    // 감소 함수
    fun decrement() {
        _repository.decrementCounter()
        _count.value = _repository.getCounter().count
    }
}

 

 

  • CounterViewModel은 ViewModel을 상속하여 View와 Model 간의 중간 역할
  • CounterRepository를 통해 현재 카운터 값을 관리하고, 변경 시 _count를 갱신
  • increment와 decrement 함수는 각각 카운터 값을 증가 및 감소시키고, 변경된 값을 count에 반영

 

결론

MVVM 패턴 요약

MVVM 패턴을 통해 UI와 데이터 로직을 명확하게 분리하고, ViewModel을 통해 데이터를 쉽게 관리할 수 있다. 이 패턴은 특히 UI 변경이 잦거나 비즈니스 로직이 복잡한 앱에서 큰 도움이 된다. 이번 Counter 앱 예제를 통해 MVVM의 구조와 장점을 직접 체험할 수 있으며, 실무에서도 효율적인 아키텍처 패턴으로 활용될 수 있다.

 

번외

스프링의 MVC패턴과 MVVM 패턴과 상당히 유사한 점을 보여준다.

아래는 이전에 작성한 MVC 관련 글이며, MVC에 대해 잘 모른다면 참고하시길 바랍니다.

2024.11.02 - [Backend/Spring || SpringBoot] - [Spring/스프링] MVC 패턴이란? 스프링 MVC와 Counter 앱 예제

 

스프링의 MVC 패턴과 비교하기

 

  • Model (MVVM) <-> Service & Repository (Spring)
    • MVVM의 Model은 애플리케이션의 데이터를 관리하고, 비즈니스 로직을 처리한다.
    • 스프링에서의 Service와 Repository 계층이 이 역할을 한다.
      • Repository는 데이터베이스와 상호작용하여 데이터를 가져오고 저장하는 역할을 한다.
      • Service는 비즈니스 로직을 담당하며, 여러 데이터 처리를 종합하고 필요한 로직을 구현
  • ViewModel (MVVM) <-> Controller (Spring MVC)
    • ViewModel은 MVVM에서 View와 Model 사이에서 데이터를 가공하고, UI와 상호작용을 중개하는 역할을 한다.
    • 스프링에서는 Controller가 이 역할을 수행
      • 스프링의 Controller는 Model과 View 사이에서 요청과 응답을 처리하고, Service와 통신하여 데이터를 가공한 후 View에 전달
    • 다만, 스프링의 Controller는 클라이언트의 요청에 응답하는 구조로 동작하고, MVVM의 ViewModel은 상태 관리를 위해 앱의 UI와 긴밀하게 연결되며, 이 점이 큰 차이다.
  • View (MVVM) <-> View (Spring - Thymeleaf, JSP 등)
    • MVVM의 View는 사용자에게 표시되는 UI로, ViewModel을 통해 데이터와 UI 상태를 바인딩하여 동적 UI를 구성
    • 스프링의 View는 템플릿 엔진(Thymeleaf, JSP 등)을 사용하여 Controller로부터 데이터를 받아 사용자에게 표시하는 역할
    • 웹 애플리케이션에서는 주로 HTML이나 템플릿 파일을 사용하여 UI를 구성하며, 직접적인 데이터 바인딩보다는 Model을 통해 데이터를 받는다.

 

 

반응형
LIST