오늘은 MVC, MVP, MVVM 패턴에 대하여 공부 및 정리 해보도록 하겠당! 우선 이 세가지 패턴의 주요한 목적은 모든 대부분의 소프트웨어 설계가 그러하듯 ‘관심사 분리(역할 분리)’이다.
MVC (Model + View + Controller)
- Model : 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)
- View: UI에 해당하는 부분
- Controller: 사용자 입력을 받고 처리 View에 연결
시나리오
- 사용자 action controller로 들어옴
- Controller는 action에 맞는 데이터 처리를 model에 요청 및 갱신
- Controller는 model을 통새 View를 갱신
장.단
- Model test용이 , 모델과 뷰 분리 용이
- 컨트롤로가 비대해지는 문제 있음
예시코드
Model.kt
class DataModel {
fun getData(): String {
return "사용자가 버튼을 클릭했습니다."
}
}
Activity
/**
* Controller 역할을 수행
* */
class MVCActivity : AppCompatActivity() {
// view 선언
val eventButton: AppCompatButton by lazy {
findViewById(R.id.eventButton)
}
val textView: TextView by lazy {
findViewById(R.id.textView)
}
// model 생성
val model = DataModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvcactivity)
// 1. 사용자 action이 controller로 들어
eventButton.setOnClickListener {
// 2. Controller는 action에 맞는 데이터 처리를 model에 요청 및 갱신
// 3. Controller는 model을 통새 View를 갱신
textView.text = model.getData()
}
}
}
Android 에서 MVC 총평
Android 에서 M은 Model class, Controller는 Activity,Fragment 등, View는 XML 정도로 생각해 볼 수 있었다. android 에서는 Activity등 android 기반 컴포넌트 들이 갖는 특수성 때문에 Activity가 View와 Controller의 역할을 모두 수행하는 측면이 있어서 어디까지를 view로 봐야할지 각 포스팅마다 의견이 분분했다..
이러한 특수성때문에 더욱이 MVC 패턴이 갖게되는 단점이 부각되는데.. 개발경험상 Activity나 Fragment등 안드로이드 컴포넌트들 즉 Controller가 지나치게 비대해진다는것..! 유지보수가 어려웠던 기억이 있다.
MVP (Model + View + Presenter)
- Model : 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)
- View : Activity등의 Android 컴포넌트를 뷰로 간주함
- Presenter : view와 model을 연결해주는 매개체 역할을 한다는점에서 Controller와 유사하지만, 인터페이스로 구성되어 의존성을 분리한다.
시나리오
- 사용자 action이 View를 통해 들어온다
- View는 action에 맞는 데이터를 presenter에 요청
- Presenter는 model에 데이터를 요청 하여 응답받고 View에 데이터 응답
- View는 전달받은 데이터로 Ui 갱신 (전달이라기보단 call)
장.단
- 인터페이스로 의존성 분리가 가능
- 안드로이드 고유 컴포넌트가 view에만 종속되도록 유지할 수 있다.
- Presenter와 View가 1:1 관계가 생겨 의존성이 강해진다
- Presenter도 마찬가지로 비대해질 수 있다.
예시코드
Model
class DataModel {
fun getData(): String {
return "사용자가 버튼을 클릭했습니다."
}
}
Activity (View)
// View역할
class MVPActivity : AppCompatActivity(), MVPPresenter.MVPView {
val eventButton: AppCompatButton by lazy {
findViewById(R.id.eventButton)
}
val textView: TextView by lazy {
findViewById(R.id.textView)
}
val mvpPresenter: MVPPresenter = MVPPresenterImpl(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mvpactivity)
// 1. 사용자 action이 View를 통해 들어온다
eventButton.setOnClickListener {
// 2. View는 action에 맞는 데이터를 presenter에 요청
mvpPresenter.getData()
}
}
// 4. View는 전달받는 데이터로 ui를 갱신
// MVPView interface 구현
override fun setDataInTextView(data: String) {
textView.text = data
}
}
Presenter Interface
interface MVPPresenter {
fun getData()
interface MVPView {
fun setDataInTextView(data: String)
}
}
Presenter Implements
class MVPPresenterImpl(val view: MVPPresenter.MVPView) : MVPPresenter {
val model: DataModel = DataModel()
// 3. Presenter는 model에 데이터를 요청 하여 응답받고 View에 데이터 응답
override fun getData() {
view.setDataInTextView(model.getData())
}
}
Android 에서 MVP 총평
MVC에서 모호했던 Android 컴포넌트를 View로 간주하고, 인터렉션을 모두 컴포넌트에 일임하여 Presenter와 Model을 Android 속성을 배제하여 테스트 및 사용할 수 있다는 이점이 있다..! 하지만 Presenter와 View 가 1:1관계를 갖게되어 재사용이 힘들고 필요한 class들이 증가하는 문제가 있어보인다. 실무에서 직접 사용해본 경험이 없어서 간단히 사용해보아 체감이 힘든 부분이있당
MVVM (Model - View - ViewModel)
Model: 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)
View: 이벤트를 ViewModel로 전달, ViewModel Data를 구독하여 UI 변경, dataBinding을 통해 유연하게 변경가능
ViewModel : Model에서 데이터를 요청하고 데이터를 외부로 노출시킨다. (View를 모름)
시나리오
- 사용자 action이 view를 통해 들어온다.
- View는 action에 맞는 data를 ViewModel에 요청
- ViewModel은 Model에 데이터를 요청 하고 observable field로 매핑하여 외부로 노출
- View는 ViewModel을 구독해서 자신을 변경
장.단
- View에 대한의존성을 완벽하게 분리할 수 있다.
- 중복되는 코드를 모듈화 할 수 있다.
- View의 역할이 상대적으로 적어진다.. 는 장점이라고 생각한다 단순히 VeiwModel의 데이터를 보여주기만 하면되서.. 확실한 의미
예시코드
Model
class DataModel {
fun getData(): String {
return "사용자가 버튼을 클릭했습니다."
}
}
ViewModel
class ViewModel: ViewModel() {
private val dataModel = DataModel()
private var _textData = MutableLiveData<String>("Loading...")
val textData: LiveData<String> = _textData
// 3. ViewModel은 Model에 데이터를 요청 하고 observable field로 매핑하여 외부로 노출
fun getData() {
_textData.value = dataModel.getData()
}
}
View (Activity)
class MVVMActivity : AppCompatActivity() {
lateinit var binding: ActivityMvvmactivityBinding
val viewModel: ViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMvvmactivityBinding.inflate(layoutInflater)
setContentView(binding.root)
// 4. View는 ViewModel을 구독해서 자신을 변경
// 이렇게 observe setting해줄수도 있고, xml에서 dataBinding 을 사용할 수 도 있다.
// setObserver()
// 4. View를 DataBinding을 이용해서 자신을 유연하게 변
// dataBinding 사용
binding.viewModel = viewModel
binding.lifecycleOwner = this
// 1. 사용자 action이 view를 통해 들어온다.
binding.eventButton.setOnClickListener {
// 2. View는 action에 맞는 data를 ViewModel에 요청
viewModel.getData()
}
}
private fun setObserver() {
viewModel.textData.observe(this) { data ->
binding.textView.text = data
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.moon.mvcmvpmvvm.mvvm.ViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout 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=".mvvm.MVVMActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.textData}"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/eventButton"
android:layout_width="0dp"
android:layout_height="50dp"
android:text="Get Data"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Android에서 MVVM 총평
사실 요즘 가장 지향하는 방식이기때문에 큰단점을 못느끼겠고.. 익숙하다!!. 옵저버 패턴에 의존하는 만큼 옵저버 패턴으로 동작할 수 있도록 추가적인 구현이 요구되었다면, 개발이 까다롭고, 구현해야하는 클래스등이 많아졌겠지만. AAC중 하나인 LiveData 등을 이용하면 구현이 쉬워 장점이 많다고 생각한당
예시코드는 개인 git에 올려두었습니다)
https://github.com/moon-i/Study/tree/main/MVC_MVP_MVVM_EX
참고 글)
https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/
'Android' 카테고리의 다른 글
[Android] RecyclerView and DiffUtil (0) | 2022.02.26 |
---|---|
Firebase Cloud Messaging (FCM) - Android (0) | 2022.02.24 |
[Android] Room - Part of Android Jetpack (0) | 2022.02.13 |
Clean Architecture - Android에 적용하기 (0) | 2022.01.23 |
Gson vs kotlinx-serialization (0) | 2022.01.17 |