ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Computer Science] MVC, MVP, MVVM
    Android 2024. 4. 4. 01:12
    반응형

    MVC, MVP 등의 MV-Whatever는 개발자가 다른 개발자와 협업을 할 때 약속된 패턴으로 개발함으로써 개발을 쉽게 할 수 있도록 만들어진 디자인 패턴이죠. 이것들이 무엇인지, 어떻게 사용하는 지 한 번 자세하게 정리할 필요가 있다고 느꼈습니다.

    MVC

    소프트웨어 아키텍처 패턴 중에서 가장 유명한 3형제 중 첫째입니다. 다른 얘기할 것 없이 바로 확인해 봅시다.

    MVC Image made by keykat
    MVC는 Model과 View의 의존성이 매우 큰 구조입니다.

     

    1. Controller는 여러 개의 View를 선택할 수 있습니다.
    2. 사용자의 입력이 Controller로 들어옵니다.
    3. Controller는 Model을 업데이트합니다.
    4. Model이 변경됨에 따라 3가지로 View를 업데이트할 수 있습니다.
      1. Model이 Notify를 날려 View를 일깨워줍니다.
      2. Model에 Observer를 달아놓은 View가 실시간으로 업데이트됩니다.
      3. View가 직접 Model을 사용해서 callback을 통해서 변경된 데이터를 받아서 업데이트합니다.
      1.  
    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

    MVP는 View와 Model의 의존성이 사라졌습니다. 대신 그 때마다 Presenter를 만들어야 하네요.

     

    1. 사용자의 input이 View에 들어옵니다.
    2. View는 자신과 연결되어 있던 Presenter에게 input에 대한 데이터를 보내달라고 요청합니다.
    3. Presenter는 Model에게 이러이러한 데이터를 달라고 요청합니다.
    4. Model은 데이터를 업데이트하고 Presenter에게 이 사실을 전달합니다.
    5. 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

     

    1. 사용자의 input이 view에 들어옵니다.
    2. 이번에는 view가 viewModel에 input에 대한 내용을 전달합니다.
    3. viewModel은 input의 내용에 따라 model에게 데이터를 request합니다.
    4. model은 이에 대해 response를 viewModel에 업데이트합니다. 이 때 Android Architecture Component (AAC)LiveData라는 녀석을 많이 사용합니다.
    5. 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에서만 중계를 열심히 해주면 됩니다. 구조적으로 많이 깔끔해진 것처럼 보입니다.

    다만.. 보시다시피 만들게 굉장히 많고 세팅할 것도 많고 알아야 할 용어나 개념도 많죠? 러닝 커브가 높고 개발 시간이 오래 걸린다는 단점이 있습니다.

     

     

    요약하자면,

    1. MVC는 만들기 쉽지만, Activity에 로직이 다 들어있고 코드가 길 수록 관리 포인트가 많아집니다. View와 Model의 의존도도 굉장히 높습니다.
    2. MVP는 View와 Model의 분리가 일어났지만, Presenter라는 애를 1:1로 다 만들어줘야 합니다. 그렇기 때문에 개발해야 할 내용이 많아지기 시작합니다.
    3. MVVM은 View와 Model의 완전 분리와 UI 변경 로직이 코드에 들어있지 않아 굉장히 깔끔합니다. 다만 알아야할 내용이 많고 개발하는 데 오래 걸릴 수 있습니다. MVP보다도 말이죠. 
    반응형
Designed and Written by keykat.