-
[Computer Science] MVC, MVP, MVVMAndroid 2024. 4. 4. 01:12반응형
MVC, MVP 등의 MV-Whatever는 개발자가 다른 개발자와 협업을 할 때 약속된 패턴으로 개발함으로써 개발을 쉽게 할 수 있도록 만들어진 디자인 패턴이죠. 이것들이 무엇인지, 어떻게 사용하는 지 한 번 자세하게 정리할 필요가 있다고 느꼈습니다.
MVC
소프트웨어 아키텍처 패턴 중에서 가장 유명한 3형제 중 첫째입니다. 다른 얘기할 것 없이 바로 확인해 봅시다.
- Controller는 여러 개의 View를 선택할 수 있습니다.
- 사용자의 입력이 Controller로 들어옵니다.
- Controller는 Model을 업데이트합니다.
- Model이 변경됨에 따라 3가지로 View를 업데이트할 수 있습니다.
- Model이 Notify를 날려 View를 일깨워줍니다.
- Model에 Observer를 달아놓은 View가 실시간으로 업데이트됩니다.
- View가 직접 Model을 사용해서 callback을 통해서 변경된 데이터를 받아서 업데이트합니다.
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding lateinit var userRepository: UserRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) binding.loginButton.setOnClickListener { userRepository.requestLogin { userName -> binding.userNameText.text = userName } } CoroutineScope(Dispatchers.IO).launch { val userData = userRepository.login().onSuccess { it -> binding.userNameText.text = it.userName }.onFailure { } } } }
마냥 나쁘지는 않지만 문제점이 있습니다. Activity 하나에 사용자 input을 받아서 model을 업데이트하고 view를 변경하는 로직이 전부 들어가 있습니다. 개발하기에는 쉽지만, 저런 코드가 한 1000줄 정도 되면 그 때부터는 관리하기가 힘들어지겠죠?
MVP
- 사용자의 input이 View에 들어옵니다.
- View는 자신과 연결되어 있던 Presenter에게 input에 대한 데이터를 보내달라고 요청합니다.
- Presenter는 Model에게 이러이러한 데이터를 달라고 요청합니다.
- Model은 데이터를 업데이트하고 Presenter에게 이 사실을 전달합니다.
- Prsenter는 Model로부터 받은 데이터를 View에게 다시 전달합니다.
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding lateinit var loginPresenter: LoginPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) binding.loginButton.setOnClickListener { CoroutineScope(Dispatchers.IO).async { loginPresenter.loginRequest().onSuccess { name -> binding.nameText.text = name }.onFailure { } } } } }
class LoginPresenter { suspend fun loginRequest(): Result<String> { // Retrofit 등을 이용해서 Model에게 업데이트하라고 request합니다. // 그 후 LoginModel을 response로 받아와서 필요한 정보만 쏙 빼와서 View에게 Result로 던져줍니다. } }
class LoginModel { val userName: String = "" }
디테일하게 가면 Model은 Retrofit 같은 걸로 Request를 날려서 특정한 형태의 Entity를 만들고 Presenter에 DTO를 날려줄텐데, 이것은 좀 더 깊은 얘기고 중요한 것은 세 부분으로 나눌 수 있게 되었습니다! Activity는 Model을 가지고 있을 필요가 없어서 View와 Model의 의존성이 사라졌고, 이러한 점에서 유지 보수에 용이해졌다는 장점이 있습니다. 다만.. 이제 뭔가 데이터를 요청하는 한 사이클을 만들 때마다 하나의 Presenter가 생기고, 자연스럽게 개발해야할 것이 상대적으로 많아졌다는 게 단점이 될 수 있습니다.
반응형MVVM
- 사용자의 input이 view에 들어옵니다.
- 이번에는 view가 viewModel에 input에 대한 내용을 전달합니다.
- viewModel은 input의 내용에 따라 model에게 데이터를 request합니다.
- model은 이에 대해 response를 viewModel에 업데이트합니다. 이 때 Android Architecture Component (AAC) 의 LiveData라는 녀석을 많이 사용합니다.
- ViewModel을 Binding한 View는 데이터가 바뀜에 따라 즉시 자신을 업데이트합니다.
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) loginViewModel = ViewModelProvider(this).get(LoginViewModel::class.java) binding.loginButton.setOnClickListener { CoroutineScope(Dispatchers.IO).launch { loginViewModel.requestLogin() } } } }
class LoginViewModel @Inject constructor( private val loginRepository: LoginRepository, ): ViewModel() { private val _userName = MutableLiveData<String>() val userName: LiveData<String> = _userName suspend fun requestLogin() { viewModelScope.async { loginRepository.requestLogin().onSuccess { _userName.value = it } } } }
class LoginRepository { suspend fun requestLogin(): Result<String> { val name = // retrofit 등으로 가져온 데이터를 정제해서 여기에 넣어줍니다. return Result.success(name) } }
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <data> <variable name="viewModel" type="com.example.myapplication.LoginViewModel" /> </data> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.userName}"/> </androidx.appcompat.widget.LinearLayoutCompat> </androidx.coordinatorlayout.widget.CoordinatorLayout>
View에 ViewModel을 하나 만들어놓고, ViewModel은 Repository에서 데이터를 꺼내와서 (여기서 Repository부터 Model입니다.) ViewModel의 userName에 넣어둡니다. XML에서는 TextView에 @{viewModel.userName}을 통해 DataBinding을 해서 LiveData가 바뀌면 실시간으로 업데이트될 수 있게 해줍니다.
이제부터 View는 Model의 정보를 알 필요도 없고, Activity에서 TextView의 text를 바꾸는 로직이 필요하지도 않으며, Model은 뭔가 request가 오면 데이터만 툭 던져주고 자기 할 일은 다한 것이고, ViewModel에서만 중계를 열심히 해주면 됩니다. 구조적으로 많이 깔끔해진 것처럼 보입니다.
다만.. 보시다시피 만들게 굉장히 많고 세팅할 것도 많고 알아야 할 용어나 개념도 많죠? 러닝 커브가 높고 개발 시간이 오래 걸린다는 단점이 있습니다.
요약하자면,
- MVC는 만들기 쉽지만, Activity에 로직이 다 들어있고 코드가 길 수록 관리 포인트가 많아집니다. View와 Model의 의존도도 굉장히 높습니다.
- MVP는 View와 Model의 분리가 일어났지만, Presenter라는 애를 1:1로 다 만들어줘야 합니다. 그렇기 때문에 개발해야 할 내용이 많아지기 시작합니다.
- MVVM은 View와 Model의 완전 분리와 UI 변경 로직이 코드에 들어있지 않아 굉장히 깔끔합니다. 다만 알아야할 내용이 많고 개발하는 데 오래 걸릴 수 있습니다. MVP보다도 말이죠.
반응형'Android' 카테고리의 다른 글
[Android] compose에 폰트 적용하기 (0) 2024.06.17 [Android] 프로젝트에 Android의 Hilt를 사용해보자 (0) 2024.05.12 [Android] Android 14: Broadcast Receiver와 Foreground Service 유형 정책 변경 (0) 2024.05.02 [Android] 버전 카탈로그로 Gradle 버전 관리하기 (0) 2024.04.24