Android MVC MVP MVVM MVI의 차이

김성환·2024년 4월 20일

안드로이드에서 앱개발을 좀더 쉽게만들고 유지보수를 도와줄수 있는 방법은 무었이 있을까요 라고 묻는다면 저는 구조(architecture) 라고 말할것 같습니다.

안드로이드에서 구조를 말한다면 MVC, MVP, MVVM, MVI 이 4가지를 들수있습니다.

MVC(Model-View-Controller)

  • Model: 데이터 및 비즈니스 로직을 담당합니다.
  • View: 사용자 인터페이스를 담당합니다.
  • Controller: 모델과 뷰 사이의 상호 작용을 관리합니다.

가장 간단한 구조로 빠르게 개발하수 있다는 장점이 있습니다. 그렇지만 앱의 규모가 커지면 커질수록 액티비티(Controller)에 코드가 많아지게되어 유지보수가 어려워집니다.

MVP(Model-View-Presenter)

  • Model: 데이터 및 비즈니스 로직을 담당합니다.
  • View: 사용자 인터페이스를 담당합니다.
  • Presenter: 모델과 뷰 사이의 중간자 역할을 하며, 비즈니스 로직을 처리하고 뷰를 업데이트합니다.

MVC에서 Activity가 View와 Controller의 역할이 였다면 MVP에서는 Activity 에서는 View의 역할만 가지고 있습니다.

interface MvpContractor {

    interface View {
        fun responseEvent(event: String)
    }

    interface Presenter {
        fun attachView(view: View)

        fun detachView()

        fun requestEvent(event:String)
    }
}

Presenter를 구현하여 View에서 Presenter에 이벤트를 전달하면 Present에서는 해당 이벤트의 처리가 완료되면 View에 응답을 전해주는 방식입니다. 이방식은 View와 Model간의 의존성이 없어지며 UI와 비즈니스 로직 분리등 UnitTest가 수월하다는 장점이 있습니다. 단점으로는 View와 Presenter 가 1대1로 연결되다보니 View가 많아지면 Presenter도 많아집니다. 또한 MVC의 단점처럼 기능이 추가될수록 Presenter가 비대해집니다.

MVVM(Model-View-ViewModel)

  • Model: 데이터 및 비즈니스 로직을 담당합니다.
  • View: 사용자 인터페이스를 담당합니다.
  • ViewModel: 뷰와 모델 간의 상호 작용을 관리하며, 뷰를 업데이트합니다.

MVVM의 특징은 ViewModel입니다. ViewModel은 ViewState를 유지합니다. 이는 ViewModel의 생명주기의 특성때문입니다.

위 그림처럼 ViewModel은 액티비티가 Configuration 변경으로 액티비티가 다시 시작 되게되더라도 ViewModel은 살아있는것을 볼수 있습니다. 이는 Activity 내부에서 Configuration 변경과 무관하게 유지 되는 NonConfigurationInstances 객체를 따로 관리하기 때문입니다.

이러한 속성덕에 ViewModel은 ViewState를 유지할수 있게 되므로 ViewModel에서 데이터를 유지할수 있게됩니다. View에서는 이러한 ViewState를 관찰하다가 변경이 있을때 변경점을 이용하여 View최신화 할수있게됩니다. 이러한점은 Data가 Activity의 생명주로 부터 안전할수있게 해주면서 View와 ViewModel의 결합도가 느슨해지며 모듈별로 개발할수 있게됩니다. 단점으로는 디자인 패턴에 비해 복잡해집니다. 이러한점때문에 규모가 작은앱에는 적절하지 못한 선택일수도 있습니다.

MVI(Model-View-Intent)

  • Model: 상태를 관리하고 상태에 따른 로직을 처리합니다.
  • View: 사용자 인터페이스를 담당합니다.
  • Intent: 사용자의 액션을 나타내며, 상태 변경을 유발합니다.

MVVM 구조에서 ViewState를 유지할수 있다고 말한적이 있습니다. 그것을 이용해 ViewState를 Intent라는 형태로 관리할수 있습니다.

sealed class MviIntent {
    object LoadImage : MviIntent()
}

sealed class MviState {
    object Idle : MviState()
    object Loading : MviState()
    data class LoadedImage(val image: Image, val count: Int) : MviState()
}
val channel = Channel<MviIntent>()

private val _state = MutableStateFlow<MviState>(MviState.Idle)
val state : StateFlow<MviState> get() = _state

해당 상태를 Channel이나 StateFlow를 이용하여 현재상태를 최신화하고 감지하여 상태에 맞는 로직을 동작하도록 구현할수있습니다.

MVI는 하나의 상태만 관리하기 때문에 상태충돌이 없고 선순환구조라 흐름을 이해하기 쉽습니다. 또한 불변객체이기 때문에 스레드에 안전하다는 장점이있지만 자은변경도 Intent를 거쳐야한다는 단점이 생기게 됩니다.

이렇게 여러 구조를 보았습니다. 각구조는 장단점이 있습니다. 예를들어 소스톱워치같이 작은 앱에서는 구조가 그렇게 필요없을수도 있습니다. 하지만 카카오톡이나 토스같은 앱에서는 기능도 많고 유지보수를 계속 해줘야하기때문에 MVC같은 구조로 앱을 만든다면 코드도 길어지고 보일러플레이트코드도 많아지기에 당연하게 기능추가나 유지보수가 힘들어질수 있습니다. 그렇기에 앱에맞는 구조를 정하는 것이 중요합니다.


reference
https://medium.com/delightroom/mvc-mvp-mvvm-mvi-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1-2442a4189c79
https://velog.io/@jojo_devstory/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4-MVP%EA%B0%80-%EB%AD%98%EA%B9%8C
https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18
https://medium.com/@rlatngus6663/android-configuration-%EA%B5%AC%EC%84%B1-%EB%B3%80%EA%B2%BD-beb25aa6ab30

0개의 댓글