지난 번에는 MVC to MVP를 작성했었다.
우테코 레벨 2 내내 MVP 패턴으로 개발했고 느낀 것들로 MVP가 좋은 점에 대해 설명했다.
그래서 이번엔 레벨 3 내내 함께한 MVVM 패턴에 대해 이야기해보려고 한다.
MVVM을 간략하게 설명하자면 Model + View + Viewmodel 이다.
MVVM도 MVP와 마찬가지로 MVC에서 파생된 패턴이라고 할 수 있다.
MVP에서 바뀐 부분은 한 가지로 Presenter가 ViewModel이 된 것이다. 이제 이 부분에 대해 살펴보자.
뷰와의 의존 관계
MVP에선 (추상화를 통해) 뷰와 프레젠터가 서로를 (느슨하게) 알고있었다.
뷰와 프레젠터 모두 서로를 인터페이스에 정의된 함수들을 통해 호출하였다.
그러므로 뷰와 프레젠터 서로에게 의존하는 양방향 의존 관계였다.
반면 MVVM은 단방향 의존 관계를 갖는다. 뷰만이 뷰모델을 알고 있다.
이때 옵저버 패턴이라는 디자인 패턴이 사용된다.
잠시 옵저버 패턴에 대해 알아보자.
옵저버 패턴에는 옵저버와 옵저버블이 존재한다. 즉 관찰자와 관찰가능한 것이 존재한다.
옵저버를 옵저버블에 등록하면 옵저버블에 변경사항이 발생했을 때 등록된 옵저버들에게 알린다.
옵저버 패턴의 구현 방법에 대해선 추후 따로 글을 작성하도록 하겠다.
안드로이드 개발에선 안드로이드의 라이브 데이터나 코틀린의 플로우가 옵저버블로 주로 사용된다.
다시 돌아와서 MVVM은 뷰가 뷰모델에 존재하는 상태들을 옵저빙(관찰)하므로
뷰만이 뷰모델을 알고 있으며, 뷰모델은 뷰를 알지 못한다.
이로써 단방향 의존 관계를 가질 수 있게 되었다.
그럼 단방향 의존 관계를 가졌을 때 좋은 점은 무엇이 있을까?
관심사 분리
MVP에서는 프레젠터에서 상태가 변경되면 필요한 뷰의 함수를 호출했다.
이는 결국 프레젠터에서도 뷰에 어떤 행동이 필요한지를 알아야 한다는 것이다.
간단한 예시를 보도록 하자. 계산기를 구현한다고 생각해보자.
먼저 프레젠터의 예시다.
class Presenter(private val view: CalculatorContract.View): CalculatorContract.Presenter {
private val calculator = Calculator()
fun minus(number: Int) {
calculator.minus(number)
view.update(calculator)
}
fun plus(number: Int) {
calculator.plus(number)
view.update(calculator)
}
}
프레젠터의 경우 더하기 혹은 빼기와 같은 행위가 이뤄지고 상태가 변경되면 프레젠터가 뷰의 행동을 지정해줘야한다.
반면 뷰모델의 예시이다.
class CalculatorViewModel() {
// Observable은 특정 구현체가 아닌 설명을 위한 추상적인 래핑 클래스다.
val calculator = Observable<Calculator>()
fun minus(number: Int) {
calculator.value = calculator.minus(number)
}
fun plus(number: Int) {
calculator.value = calculator.plus(number)
}
}
뷰모델은 더하기 혹은 빼기와 같은 행위가 이뤄지면 상태가 변경되는 것이 전부이다. 다른 일은 벌어지지 않는다.
대신 뷰에서 뷰모델의 상태를 옵저빙하여 변화를 감지하고 변화에 따라 뷰를 변경한다.
프레젠터와 뷰모델 모두 뷰의 상태에 대한 비즈니스 로직을 수행하는 곳이다.
뷰는 단순히 뷰를 그리고 변경하는 역할만 수행하고 프레젠터와 뷰모델은 비즈니스 로직이 수행되는 것이다.
그러나 방금 예시를 본다면 비즈니스 로직만을 수행하는 뷰모델과 달리
프레젠터에는 뷰를 업데이트하는 코드 또한 작성되었다.
이런 측면에서 뷰모델이 프레젠터보다 관심사 분리가 더 잘 된다고 볼 수 있을 것이다.
뷰에서 필요한 값들은 그대로지만 뷰의 생김새와 동작 방식이 일부 변경되었다고 해보자.
프레젠터 혹은 뷰모델에서 갖는 상태 값은 그대로지만 뷰에서 상태 값이 필요한 시점이 달라지게 되었다.
이때 MVP라면 프레젠터에서 뷰에게 값을 전달해주므로 변경이 필요할 것이다.
반면 MVVM이라면 뷰모델은 뷰가 시키는대로 일만 할뿐 변경되지 않을 것이다.
물론 이것은 이상적인 이야기다. 뷰가 변경되면 뷰모델도 언제든 변경될 수 있다.
하지만 적어도 뷰모델로부터 뷰에 대한 우리의 시선을 확실히 분리할 수 있다는 정도에 의미를 두도록 하자.
테스트
이전 글에서 봤듯이 MVP는 MVC에 비해 테스트에서 강점을 갖는다.
그럼 MVVM은 어떨까?
MVVM 또한 뷰모델을 테스트함으로써 테스트가 가능하다. 또한 프레젠터 테스트와 굉장히 유사하다.
다만 한 가지 차이점이 있다면 바로 뷰가 사라졌다는 점이다.
뷰모델은 뷰를 모르기 때문에 프레젠터 테스트와 같이 뷰를 모킹할 필요가 없다.
뷰를 모킹하고 목객체의 함수 행위들을 지정해줄 필요가 없어진 것이다. 훨씬 간편해졌다고 말할 수 있다!
느꼈을지 모르지만 테스트에서의 이점 또한 관심사 분리에서 온 이점이다.
뷰의 관심사를 완전히 덜어냈기 때문에 뷰를 모킹하지 않아도 된다는 테스트의 이점을 가져온 것이다.
// 테스트 코드 예시도 추가하고 싶지만.. 나중에 하도록 하겠다.
느낀점
읽으면서 느낄 수도 있었겠지만 MVC to MVP와 비교했을 때 글의 전개 방식이 굉장히 다르다.
이유는 그때와 달리 극적인 차이를 느끼지는 못했기 때문이다.
MVC에서 MVP로 변경했을 땐 극적인 차이가 느껴졌고 글로 풀어내기도 쉬웠다.
그러나 MVP와 MVVM을 비교했을 땐 분명 차이는 있었지만 극적으로 느껴지진 않았고 글로 전달하기도 더 어려웠다.
사용하면서 느낀점은 MVP와 MVVM이 꽤 유사하고
MVVM은 옵저빙 패턴이 더해진 MVP라고도 말할 수 있을 것 같다.
필자는 안드로이드 개발자이므로 지금까지 패턴들에 대해 했던 이야기들이 안드로이드에 편향되었을 수도 있다.
아쉽게도 다른 프론트엔드 프레임워크는 경험해보지 못했기 때문이다.
(그래도 AAC ViewModel을 MVVM의 뷰모델이라고 생각하진 않으므로 그런 의심은 거더두어도 좋다)
MVVM에 대해 이야기하다 혹자에게 이런 말을 들었다.
"MVVM이라면 바인딩 레이어를 이용해서 디자이너가 디자인할 수 있다!"
안드로이드 측면에서 좀 더 풀어서 설명하자면
바인딩 어댑터를 모두 만들어놓으면 디자이너가 xml로 뷰를 디자인할 수 있다는 말이다.
즉 개발자가 xml로 뷰를 만들지 않고 비즈니스 로직을 다루면 된다는 말이다.
하지만 필자에겐 지금까지 들었던 모든 이상적인 이야기 중 가장 이상에 가까운 이야기였고
이야기를 꺼낸 혹자 또한 동의했다.
결국 xml과 같은 뷰 레이아웃은 개발자가 만들게 될 것이라고 생각하기 때문에 필자는 안드로이드에서 데이터 바인딩을 막 선호하지는 않는다.
누군가는 MVVM에서 바인딩 레이어가 필수라고 말한다.
필자는 단순히 이론적인 이야기 보단 이론을 직접 적용해보면서 오는 경험을 기반으로 생각하는데
필자의 경험상 바인딩 어댑터의 가독성이 꽤나 떨어진다고 생각하고, 바인딩 어댑터가 생기면 결국 뷰를 이해하기 위해 봐야할 파일이 하나 더 늘어난다고 생각하기 때문에 바인딩 어댑터를 사용하지 않는 편이다.
물론 이것은 필자의 개인적인 의견이며, 필자 또한 앞으로 다른 여러 경험을 하면서 생각이 바뀔 수도 있다.
혹시 바뀌게 된다면 그때 새로운 글을 적도록 하겠다.
'디자인 패턴' 카테고리의 다른 글
Why MVP (부제: MVC to MVP) (1) | 2023.05.30 |
---|