MVVM 패턴이란?

MVVM 패턴은 앱의 구성 요소를 Model, View, ViewModel로 나눈 아키텍처를 말한다. View를 비즈니스 로직으로부터 분리시키고, ViewModel에서 디스플레이 로직을 제외한 대부분의 것들을 처리하는 방식으로 관심사를 분리하여 각 계층 간의 의존성을 최소화하는 방법이다.

MVVM, MVP, MVC architecture

MVC, MVP 패턴과 비교했을 때, MVVM은 각 계층간에 양방향 의존이 없는 것을 확인할 수 있다. ViewModel은 View의 존재를 모르고 View와 무관하게 동작하기 때문에 View가 다른 View로 대체되어도 사이드 이펙트가 발생하지 않는다. 마찬가지로 View 또한 ViewModel의 존재를 모르기 때문에 어떤 ViewModel에서든 사용될 수 있다.

안드로이드에서의 MVVM 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해주는 안드로이드 앱이 있다고 가정하고 Model, View, ViewModel에서 하는 역할을 간단한 코드로 작성한 결과이다.

 

예시 코드에서는 View와 ViewModel의 중개자 역할로 RxJava의 Observable을 이용했다. ViewModel은 Model로부터 가져온 데이터를 Observable::onNext를 통해 발행하고, View는 해당 Observable을 구독해서 데이터를 처리하게 된다.

View

// 사용자 입력 ViewModel로 전달
button.setOnClickListener({
    showProgress()
    viewModel.findAddress(editText.text.toString())
})
// Observable 구독을 통한 결과 처리
viewModel.resultListObservable.subscribe({
    hideProgress()
    updateList(it)
})

ViewModel

fun findAddress(address: String) {
    model.fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onSuccess = { entityList ->
                resultListObservable.onNext(entityList)
            },
            onError = { e ->
                resultListErrorObservable.onNext(e as HttpException)
            }
        )
        .addTo(disposable)
}

Model

fun fetchAddress(address: String): Single<List<Entity>> {
    return getRetrofit().fetchAddressFromServer(address)
}

MVVM 패턴의 장단점

MVVM 패턴은 MVP 패턴과 비교해 많은 이점을 갖는다. 각 계층의 관심사 분리가 확실해질 뿐만 아니라 유지보수성, 확장성, 테스트 용이성도 뛰어나다. 또한, View-ViewModel, ViewModel-View 사이의 상호 의존이 사라지면서 프로젝트 규모가 커질 경우, MVP보다 작성해야 할 코드의 양이 감소한다. RxJava/Android, Eventbus, LiveData 등을 이용한 Event-driven 방식의 처리에 익숙하다면 앱을 설계, 개발할 때 가장 유리한 구조가 아닐까 싶다.

MVP 패턴이란?

MVP 패턴은 앱의 구성 요소를 Model, View, Presenter로 나눈 아키텍처를 말한다.

이전 포스팅에서 MVC는 Controller가 Model의 데이터를 View로 직접 전달하지 못하고, View가 직접 Model에 데이터를 요청해야 한다는 단점이 있었는데, 이를 개선한 것이 MVP이다. MVP 패턴의 Presenter는 View와 Model 사이에서 Controller보다 좀 더 나은 중재자 역할을 수행한다.

 

MVP architecture

 

위 그림과 같이 Presenter는 View에서 요청한 입력에 대한 결과를 Model로 부터 가져와 다시 View에 전달해 줌으로써 View와 Model의 의존 관계를 제거하고, View는 오로지 Presenter가 전달해 준 데이터를 어떻게 표시할지에 대해서만 다룰 수 있게 된다.

안드로이드에서의 MVP 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해주는 안드로이드 앱이 있다고 가정하고 Model, View, Controller에서 하는 역할을 간단한 코드로 작성한 결과이다.

View

// 사용자 입력 Presenter로 전달
button.setOnClickListener({
    presenter.findAddress(editText.text.toString())
    controller.findAddress(editText.text.toString())
})

Presenter

fun findAddress(address: String) {
    model.fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onStart = {
                view.showProgress()
            },
            onSuccess = { entityList ->
                view.hideProgress()
                view.updateList(entityList)
            },
            onError = { e ->
                view.hideProgress()
                view.showErrorMessage(e.message)
            }
        )
        .addTo(disposable)
}

Model

fun fetchAddress(address: String): Single<List<Entity>> {
    return getRetrofit().fetchAddressFromServer(address)
}

MVC 패턴과 비교하여 동일한 동작을 MVP로 구현했을 때, 아래와 같은 이점이 있음을 확인할 수 있다.

  • View가 Presenter에만 의존적이다.
    MVC에서는 View가 Controller와 Model을 모두 알고있어야 했다.
  • Model의 역할이 줄어들었다.
    Model은 API를 호출하고 데이터를 반환하는 역할만 한다.
    UI 로직, 스레드 관련 로직들은 모두 Presenter에서 다룬다.

MVP 패턴의 장단점

장점: 테스트, 모듈화를 쉽게 할 수 있다.

단점: 많은 양의 보일러플레이트 코드가 생긴다. (View의 생명주기에 따른 네트워크 요청 취소 처리 등..)

MVC 패턴이란?

전통적인 아키텍처 패턴으로 UI를 가지는 앱을 개발하기 위해 앱의 구성 요소를 Model-View-Controller(MVC)로 나누는 방법이다. 하지만, MVC 패턴이 가지는 명확한 한계점 때문에 MVP나 MVVM 패턴에 비해 자주 사용되지는 않는다.

 

MVC에서 View는 데이터를 UI에 보여주는 로직만을 알고 있다. 사용자 입력에 반응하여 View는 Controller에게 입력을 전달하고 Controller는 입력에 알맞은 데이터를 Model에게 요청한다. 데이터가 준비되면 Controller는 View에게 이 사실을 알리고 View는 Model로부터 필요한 데이터를 가져와 UI에 표시해 준다.

 

이러한 흐름은 아래와 같이 표시할 수 있다.

MVC architecture

MVC는 View가 Model의 데이터가 사용 가능 여부를 판단하는 방식에 따라 두 종류로 나뉜다.

  • Passive MVC: Controller가 View에게 Model의 데이터가 갱신되었음을 알림
  • Active MVC: View가 직접 Model의 Observable 데이터를 구독함으로써 데이터 갱신을 처리함

안드로이드에서의 MVC 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해 주는 안드로이드 앱이 있다고 가정하고 Model, View, Controller에서 하는 역할을 간단한 코드로 작성한 결과이다.

 

구현을 시작하게 되면 'Activity? Fragment? 어떤 안드로이드 컴포넌트를 View로 사용해야 하지?'라는 고민을 하게 될 수도 있다. View는 UI를 표시하고 유저와 상호작용을 하는 역할을 하므로 Activity, Fragment 모두를 View로써 사용할 수 있다.

View

// 사용자 입력 Controller로 전달
button.setOnClickListener({
    controller.findAddress(editText.text.toString())
})

Controller

fun findAddress(address: String) {
    view.showProgress()
    model.findAddress(address)
}

fun doWhenResultReady() {
    view.hideProgress()
    view.showResult()
}

fun doWhenThereIsErrorFetchingTheResult() {
    view.hideProgress()
    view.showError()
}

Model

fun findAddress(address: String) {
    fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onSuccess = { entityList ->
                list = entityList
                controller.doWhenResultReady()
            },
            onError = {
                controller.doWhenThereIsErrorFetchingTheResult()
            }
        )
        .addTo(disposable)
}

MVC 패턴의 장단점

MVC 패턴을 도입하게 되면 테스트 용이성, 유지 보수성, 확장성이 증가하고 SOLID 원칙 중 단일 책임 원칙을 만족시킬 수 있다는 장점이 있지만 아래와 같은 단점 또한 존재한다.

  1. View가 Controller, Model 모두에 의존적이다.
    UI에 필요한 데이터를 표시하기 위해 View는 Controller에 데이터를 요청해야 하고, 준비 완료된 데이터를 Model로부터 받아와야 한다. 이는 View가 Controller, Model 모두를 알고 있어야 함을 의미한다.
  2. Model의 역할이 너무 광범위하다.
    위의 예시 코드를 보면 Model은 단순 데이터를 만드는 것뿐만 아니라 네트워크 연결, 컨트롤러로의 알림 역할까지 하고 있다. 앱이 비활성화되거나 화면 전환 등이 일어날 경우, Model에서 데이터 요청 작업에 대한 취소 처리까지 해야 함을 의미한다.
  3. View가 UI 로직으로부터 자유롭지 못하다.
    MVC에서는 View가 요청한 결과 데이터를 '어떻게' 보여줄지를 결정한다. View는 UI에 데이터를 보여주고 사용자 입력을 받는 역할만 해야 하는데 '어떻게'에 대한 로직이 포함되므로 좋은 구조라고 할 수 없다. 그렇다고 이 로직을 이미 너무 많은 역할을 하고 있는 Model에 넘길 수도 없다.

결론

MVC 패턴을 사용하면 아키텍처가 없는 것보다 테스트 용이성, 유지보수성, 확장성이 증가하지만 Controller가 View에 표시할 데이터를 직접 전달해 주지 않기 때문에, Controller가 View에 데이터 준비 완료를 알려주면 View가 직접 Model에서 데이터를 읽어와야 하는 구조적 한계점이 있다. 또한, View와 Model이 너무 많은 역할을 맡게 된다는 문제점이 있기 때문에 MVP, MVVM 패턴에 비해 자주 사용되지는 않는다.

+ Recent posts