Android

Android MVC, MVVM, MVP

들풀민들레 2017. 7. 20. 21:33

 

MVVM, MVP를 적용한 Android App 개발의 코드적인 디테일은 작성자에 따라 다르다. 

특정 Framework을 이용한 적용이 아님으로 구현 방법에 차이가 날수 밖에 없을것 같다. 

 

원래 예전부터 있었던 개념인데 최근에 와서 강의 질문으로 많이 들어온다.

질문을 하는 사람들의 대부분이 서버사이드 MVC 모델을 생각하면서 MVVM 과 MVP를 이해하려 하다 보니 이해가 잘 안되는 가보다.

과감히 이렇게 이야기 하고 싶다.

Controller를 버려라. 서버사이드에서나 중요한 개념이다. 안드로이드는 클라이언트 사이드이다. 

 

MVVM 에서의 ViewModel과 MVP에서 Presenter의 역할에 집중한 글이며 각각의 구현 모습은 차이가 있을것이다.

샘플은 Kotlin으로 작성하였다.

 

 

 

 

MVC, MVVM, MVP 에 대한 생각

 

l  MVC 모델은 화면 출력을 주 목적으로 하는 소프트웨어 개발 분야에 적용하기 위한 모델이다.

l  MVC 모델은 변종이 많다.

 

대부분의 개발자들(특히 Android 개발자들 서버사이드 웹 어플리케이션에 의해 자바에 대한 경험이 있는 )MVC 모델을 접한 것은 서버사이드 웹 어플리케이션이다.

하지만 MVC 모델은 원론적인 목적이 화면 출력을 주 목적으로 하는 곳에 적용하기 위한 소프트웨어 개발 모델임으로 클라이언트 사이드에서 먼저 사용했던 모델이다.

그럼으로 서버사이드 MVC 구조를 그대로 안드로이드 같은 클라이언트 사이드 어플리케이션에 적용해 이해하려 하다 보니 이해가 안되고 어려워 보이는 거다.

 

클라이언트 사이드와 서버 사이드는 구조 자체가 다르다.

업무의 시작이 어디서 발생하는가? View는 일회성인가, 장시간 메모리에 지속되는가? 에 따라 MVC 구조는 다르게 적용될수 있다. 서버 사이드 웹의 경우에는 항상 클라이언트 request가 들어오는 경우 업무가 발생한다. 그럼으로 이 request를 분석하는 것이 주 업무인 Controller 부터 업무가 시작된다고 볼수 있고 그런 이유로 항상 Controller 중심으로 개발된다.

하지만 클라이언트 사이드 어플리케이션의 업무는 대부분 화면에서 발생하는 유저 이벤트에 의해 업무가 시작된다. 그 이벤트가 발생하는 곳은 View이다. View 부터 업무가 시작된다고 볼수 있다.

 

Android 같은 클라이언트 사이드의 어플리케이션에서 MVC를 이해할때는 Controller 개념을 버리는 것이 좋겠다. 사실 내가 보았던 클라이언트 사이드 MVC Framework(Javascript )의 경우에는 아예 Controller 라는 용어를 사용하지 않는 것도 많다.

 

또한 MVC 모델은 변종(나쁜 의미가 아니라) 이 많다. MVC의 특성상 항상 ModelView는 존재한다. 하지만 Controller의 역할이 미약하거나 필요 없는 경우 Controller를 가지지 않는 프레임워크도 많으며 또한 만약 존재한다면 역할에 따라 이름을 Controller가 아닌 다른 이름을 쓰는 경우도 많다. 그래서 아예 MVC 가 아닌 MV* 혹은 MVW(MVWhatever) 라고 부르기도 한다.

 

안드로이드에서 많이 화자되는 MVP, MVVM 의 경우도 결국 역할에 따라 MVC를 변형시킨 모델이 되며 역할에 맞는 이름인 Presenter, ViewModel 이라는 이름을 사용하는 것이다.

그리고 이 MVP, MVVM은 안드로이드 때문에 나온 모델이 아니다. 오래전에 MS에서 MVC의 변형 모델로 제시 했던 것이고 이 모델이 안드로이드 개발에서 화자 되는 것 뿐이다.

 

 

 

 

MVC

 

클라이언트 사이드에서 필요한 역할이 무엇인가?

 

l  유저 화면을 제공

l  화면에서 발생한 이벤트 처리

l  이벤트에 의한 업무로직 수행

l  업무로직 결과에 의한 화면 업데이트

 

 

이정도의 내용이 들어간다. 이를 하나의 Activity에서 작성한다면 가능하기는 하다

 

하나의 Activity에 너무 많은 내용이 들어가는 것을 반기 사람은 없을 것이다. 이제 위의 내용을 분리 해본다.

 

l  화면 구성은 xml 파일로

l  업무 로직은 Model 클래스로

 

 

 

위의 구조를 굳이 MVC 모델이라고 불러야 하는지는 모르겠다. 그냥 흔히 작성하는 스타일이다. Activity에서 구현되는 화면을 layout xml 로 분리 시키고 업무로직 수행을 Model 클래스로 분리 시켜서 Activity가 중심이 되어 layout xmlModel 클래스를 이용하는 구조가 된다. 또한 경우에 따라 Model에서 업무수행 결과 데이터를 직접 View에 적용시킬수도 있다.

 

코드로 보자. 샘플은 유저 입력에 의한 toto 목록을 ListView로 출력하는 기능이다. toto 데이터 저장은 DBMS를 사용하고 있다.

 

우선 View. 그냥 평범한 layout xml 파일이다.

 

View

 

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

 

    <ListView

        android:id="@+id/listView"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"/>

 

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal"

        android:layout_alignParentBottom="true">

        <EditText

            android:id="@+id/editView"

            android:layout_width="0dp"

            android:layout_height="wrap_content"

            android:layout_weight="1"/>

        <Button

            android:id="@+id/button"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="add"/>

    </LinearLayout>

 

 

</RelativeLayout>

 

 

Model

이번에는 Model 클래스.

Activity의 요청에 의해 업무로직 수행 및 수행 결과에 의한 데이터 표현을 주 목적으로 한다.

 

class MainModel(val context: Context) {

    fun getListDatas(): ArrayList<String> {

        val datas: ArrayList<String> = ArrayList<String>()

        //dbms

        val helper=DBHelper(context)

        val db=helper.readableDatabase

        val cursor = db.rawQuery("select * from tb_test", null)

        while(cursor.moveToNext()){

            datas.add(cursor.getString(1))

        }

        db.close()

        return datas

    }

    fun addItem(item: String){

        val helper=DBHelper(context)

        val db=helper.writableDatabase

        db.execSQL("insert into tb_test (todo) values (?)", arrayOf(item))

        db.close()

    }

}

 

 

 

특별해 보이지 않는다. 그냥 개발자 임의 클래스 정도로 봐도 될 것 같다.

 

Controller

 

그냥 Activity 클래스이다. Activity가 실행되거나 유저 이벤트가 발생되었을 때 적절한 Model을 실행하고 실행 결과에 의한 View를 업데이트 하는 기능을 가지고 있다.

 

 

class MVCMainActivity : AppCompatActivity(), View.OnClickListener {


    lateinit var datas: ArrayList<String>    lateinit var adapter: ArrayAdapter<String>
    val model=MainModel(this)
    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)
        button.setOnClickListener(this)
        datas=model.getListDatas()        adapter=ArrayAdapter(this, android.R.layout.simple_list_item_1, datas)        listView.adapter = adapter
    }
    override fun onClick(v: View?) {        val data: String = editView.text.toString()        model.addItem(data)
        datas.add(data)        adapter.notifyDataSetChanged()        editView.setText("")    }}
 

 

 

이런식으로 개발이 되는데 이를 굳이 MVC로 불러야 할까 하는 생각이 든다. 그냥 흔한 프로그램의 구조이다.

 

 

 

MVVM

 

MVVM Model, View, ViewModel 의 구조이다.

ModelViewMVC 의 개념과 동일하다. Model은 업무로직 수행과 수행 결과에 대한 데이터 표현이 주 목적이고 View는 화면이 주 목적이다.

MVVM 에서 가장 중요한 개념은 ViewModel 이라는 개념이다. 이 개념의 핵심은 ModelView를 분리시키겠다는 의미이다. ModelView가 직접 연결되지 않게 하기 위함이다.

 

MVVM에서 ViewModel은 아래의 역할을 담당한다.

 

l  View가 필요한 DataCommand 제공

 

ViewModel은 이름 그대로 View가 필요한 모델의 개념으로 View에서 필요한 이벤트 핸들링과 업무로직 수행 결과에 의한 데이터 바인딩을 위임 받은 클래스 정도로 이해하면 된다.

View에서 이벤트가 발생했을 때 ViewModel을 실행시키며 ViewModel에서 적절한 Model을 실행시키고 Model 실행 결과에 의한 데이터를 가지게 된다. 그리고 그 데이터에 의한 화면 업데이트를 담당 하게 된다. 결국 View에 작성되어야 하는 이벤트에 의한 업무처리와 업무 처리 결과에 의한 화면 업데이트를 위임받은, View 입장에서 보면 Model(실제 업무로직 처리하는 Model은 따로 있지만) 개념이다. 

 

 

 

 

샘플예를 보자.

 

 

View(Activty)

 

class MVVMMainActivity : AppCompatActivity(), View.OnClickListener {

 

    val viewModel: MainViewModel= MainViewModel(this)

 

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

 

        button.setOnClickListener(this)

 

        viewModel.onCreate()

    }

 

    override fun onClick(v: View?) {

        val data: String = editView.text.toString()

        editView.setText("")

 

        viewModel.onClick(data)

 

    }

 

}

 

 

 

 

화면만 관계한다. 라이프사이클 혹은 이벤트에 의한 Model 수행은 ViewModel에 위임한다.

 

 

 

ViewModel

 

 

 

class MainViewModel(val activity: Activity){
    lateinit var datas: ArrayList<String>    lateinit var adapter: ArrayAdapter<String>
    val model=MainModel(activity)
    fun onCreate(){        datas=model.getListDatas()        adapter= ArrayAdapter(activity, R.layout.simple_list_item_1, datas)        activity.listView.adapter = adapter    }
    fun onClick(data: String){
        model.addItem(data)        datas.add(data)        adapter.notifyDataSetChanged()    }}
 

 

 

실제 업무를 수행하는 Model을 실행하고 실행 결과에 의한 ViewData Binding을 제공한다.

 

View에 대한 위임의 개념임으로 ViewModel에는 ViewLifeCycle 함수(ActivityLifeCycle), Activity 객체, View의 이벤트 함수가 정의될수도 있다.

ViewLifeCycle(onCreate, onResume ) 시에 실행될 업무를 위임 받을수 있으며 View에 이벤트 발생시 실행될 업무를 위임 받을수 있기 때문에 이에 대한 함수가 정의될수 있다. 또한 Model 결과에 의한 데이터 바인딩이 직접 될수 있다.

 

 

MVP

 

MVPModel, View, Presenter 의 구조이다.

Model. ViewMVC의 개념과 동일하다. 또한 MVVM 과 마찮가지로 ModelView가 직접 연결되지 않기 위해 Presenter를 둔다. 단지 PresenterViewModle의 차이는 역할의 차이다.

 

MVP에서 Presenter는 아래의 역할을 담당한다.

 

l  View에서 발생한 이벤트에 의한 Model 실행(View->Presenter->Model)

l  Model의 실행 결과에 의한 View 실행(Model->Presenter->View)

 

결국 Presenter는 전체 구조에서 특별한 역할을 담당한다기 보다는 ModelView의 가교 역할이다. View에서 발생한 유저 이벤트에 의해 Model실행, Model의 실행 결과에 의해 View 실행으로 ModelView가 직접 연결되지 않게 하기 위함이다.

 

 

ViewModel과의 가장 큰 차이는 Presenter는 직접 View를 핸들링 하지는 않는다는 점이다. 작성 방법에 차이는 있지만 Presenter를 단순 Interface 정도로 인지하고 작성하는 사람이 있을 정도로 특정 역할을 담당하지 않는다

 

 

샘플예를 보자.

 

 

Presenter

 

interface IMainPresenter {

    fun getListView(): ArrayList<String>

    fun addItem(item: String)

    interface IView {

        fun updateListView(item: String)

    }

 

}

 

 

 

presenter를 위해서 인터페이스를 선언한다. 인터페이스의 추상 함수는 View에서 Model을 이용하기 위해서 호출되는 함수이다. 그리고 IMainPresenter 안의 내장 인터페이스인 IViewView에서 구현해야하는 인터페이스이며 updateListViewpresenterModel의 수행 결과를 View에 전달할 목적으로 호출한다.

 

 class MainPresenter(val context: Context): IMainPresenter {

 

    var model: MainModel= MainModel(context)

    lateinit var view: IMainPresenter.IView

 

 

    override fun getListView(): ArrayList<String> {

        return model.getListDatas()

    }

 

    override fun addItem(item: String) {

        model.addItem(item)

        view.updateListView(item)

    }

}

 

 

Presenter 내용이다. getListView()addItem() 함수는 View에서 호출하며 이곳에서 적절한 Model을 실행 시킨다. 또한 addItem() 함수 내부에서 ViewupdateListView() 를 호출하여 Model 이 수행된후의 적절한 View 업데이트 작업이 이루어 지게 한다.

 

결국 위의 Presenter는 업무로직을 수행하지도 않으며 View를 업데이트 하지도 않는다. 단지 ViewModel의 매개 역할만 수행한다.

 

 

View(Activity)

 

class MVPMainActivity : AppCompatActivity(), View.OnClickListener, IMainPresenter.IView{

 

    val presenter: MainPresenter = MainPresenter(this)

 

    lateinit var datas: ArrayList<String>

    lateinit var adapter: ArrayAdapter<String>

 

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

 

        button.setOnClickListener(this)

 

        presenter.view=this

 

        datas=presenter.getListView()

        adapter= ArrayAdapter(this, android.R.layout.simple_list_item_1, datas)

        listView.adapter = adapter

    }

 

    override fun onClick(v: View?) {

        val data: String = editView.text.toString()

        presenter.addItem(data)

 

    }

    override fun updateListView(item: String) {

        datas.add(item)

        adapter.notifyDataSetChanged()

    }

 

 

}

 

 

 

 

Activity의 내용이며 View의 표현을 주 목적으로 한다. Model 수행은 Presenter를 이용하며 Model 수행 결과에 의한 화면 업데이트를 위해 인터페이스를 구현하고 PresenterupdateListView() 함수를 호출하면 적절한 화면 업데이트 작업을 진행한다.