본문 바로가기

Android

Android 에서 MVC, MVP, MVVM 예제로 공부하기

오늘은 MVC, MVP, MVVM 패턴에 대하여 공부 및 정리 해보도록 하겠당! 우선 이 세가지 패턴의 주요한 목적은 모든 대부분의 소프트웨어 설계가 그러하듯 ‘관심사 분리(역할 분리)’이다.

MVC (Model + View + Controller)

출처 -  https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

  • Model : 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)
  • View: UI에 해당하는 부분
  • Controller: 사용자 입력을 받고 처리 View에 연결

시나리오

  1. 사용자 action controller로 들어옴
  2. Controller는 action에 맞는 데이터 처리를 model에 요청 및 갱신
  3. 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)

출처 -  https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

  • Model : 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)
  • View : Activity등의 Android 컴포넌트를 뷰로 간주함
  • Presenter : view와 model을 연결해주는 매개체 역할을 한다는점에서 Controller와 유사하지만, 인터페이스로 구성되어 의존성을 분리한다.

시나리오

  1. 사용자 action이 View를 통해 들어온다
  2. View는 action에 맞는 데이터를 presenter에 요청
  3. Presenter는 model에 데이터를 요청 하여 응답받고 View에 데이터 응답
  4. 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)

출처 -  https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

Model: 데이터, 데이터 처리, 비지니스로직 (독립적 재사용가능)

View: 이벤트를 ViewModel로 전달, ViewModel Data를 구독하여 UI 변경, dataBinding을 통해 유연하게 변경가능

ViewModel : Model에서 데이터를 요청하고 데이터를 외부로 노출시킨다. (View를 모름)

시나리오

  1. 사용자 action이 view를 통해 들어온다.
  2. View는 action에 맞는 data를 ViewModel에 요청
  3. ViewModel은 Model에 데이터를 요청 하고 observable field로 매핑하여 외부로 노출
  4. 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://beomy.tistory.com/43

https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/