[Android] 프레임워크 패턴 정리(MVC, MVP, MVVM, VIPER)

안드로이드 패턴

  • MVC, MVP, MVVM 이 가장 많이 쓰이는 패턴들이다.
  • 협업, 유지보수, 테스트가 용이해진다.
  • 화면에 보여주는 로직과 실제 데이터가 처리 되는 로직을 분리하는게 목적
  • 패턴을 적용하지 않고 하나의 Activity 에서 표현한다면 이와 같다.
    • 화면 제공
    • 유저 이벤트
    • 업무 로직
    • 화면 업데이트
  • 각 패턴의 재료

    • Data Object (자료 객체)

      • 기본이 되는 데이터의 순수 객체 표현
    • Business Logic (비즈니스 로직)

      • 데이터를 생성/표시/저장/변경 하는 로직
      • 대부분의 Use Case 가 구현될 부분
    • Coordinator

      • 이벤트 핸들링이나 Business Logic을 제외한 기타 로직
      • 외부 API 호출이나 DB 호출도 들어갈 수 있다.
    • Converter

      • 로컬에 존재하는 Domain Data 를 표현 계층의 데이터 형태로 Formatting 하는 역할
      • 복잡하지 않으면 Controller 나 Abstract View 안으로 들어갈 수 있음
    • Abstract View

      • 실제 화면에 표시되기 전의 추상화된 데이터
    • View Logic

      • 추상화되어있는 데이터가 화면에 어떻게 표현될지 구체적 로직을 구성함
    • View

      • Logic 이 존재하지 않는 디자인을 불러오기위한 역할
    • Event Bundle

      • 사용자 입력이나 외부 요인에 의한 데이터 변경을 정의
      • View 또는 Data 쪽으로 흡수 될 수 있다
    • External Interface

      • DB 접근, API 호출 등 외부 시스템과의 연동 인터페이스.

MVC (Model + View + Controller)


  • Model
    • 실제 데이터 및 데이터 조작 로직을 처리하는 부분
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()
}
}
  • View
    • 사용자에게 제공되어 보여지는 UI 부분
    • layout(xml) 만을 View 로 정의한다.
    • Activity, Fragment 의 경우에는 Controller 로 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>
  • Controller
    • 사용자의 입력을 받고 처리하는 부분
    • Activity, Fragment 가 이러한 역할을 대신함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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("")
}
}
  • 로직
    1. Controller 로 사용자의 입력을 받음
    2. 사용자의 입력으로 인해 Model 값을 변경해야하는지 검사한다. (T)
    3. Controller 에서 Model 의 데이터를 가져오거나 갱신한다.
    4. Controller 에서 UI 를 갱신한다.

MVVM (Model + View + ViewModel)


  • Model
    • 업무로직을 수행하고 결과에 대한 데이터를 표현한다.
  • View
    • 화면 표시가 주 목적
    • Activity, FrameWork 역할
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
}
}
  • ViewModel
    • Model 과 View 를 분리 시키는게 목적
    • View 가 필요한 Data 와 Command 제공
    • Actiity 객체나 onCreate 등 LifeCycle 업무가 포함될 수 있다.
    • Model 결과에 의한 데이터 바인딩이 직접 될 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()
}
}

MVP (Model + View + Presenter)


MVP 패턴의 가장 큰 의의는 View와 Model을 완전하게 분리해서 사용 할 수 있어야 한다.

  • interface
    • 순수한 interface를 정의한다.
    • Constant를 정의할때도 interface를 활용한다.
    • Model, View, Presenter에서 사용할 function들을
      interface 형태로 정의한다.
1
2
3
4
5
6
7
8
interface IMainPresenter {
fun getListView(): ArrayList<String>
fun addItem(item: String)

interface IView {
fun updateListView(item: String)
}
}
  • Model

    • presenter에 정의된 함수들에 대한 내용을 작성한다.
    • data class 를 정의한다.
  • View

    • Presenter 에 이벤트 핸들링을 위임한다.
      • Activity 가 View interface 를 구현해서 Presenter에서 코드를 만들 인터페이스를 갖도록 하면 된다.
    • Activity / Fragment 에 속하는 분류는 전부 View에 속한다.
    • interface 폴더에서 만든 view의 interface를 가지고 있음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()
}
}
  • Presenter

    • model에서 사용할 interface를 선언하여 각자 할일을 정의해준다.
      (view에서 직접 override 하기도 함)

    • interface를 가져와서 override한뒤 필요한 함수들을 넣어서 View 의
      상호작용에 필요한 함수들을 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
 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)
}
}

VIPER (View + Interactor + Presenter + Entity + Router)


  • View
    • 사용자 입력을 Presenter 로 보내는 작업
    • UI View Controller 에 해당
    • View Life Cycle : Life Cycle 관련 함수가 호출되는것을 Presenter 에 알림
    • View Event : Event 관련 함수가 호출되는 것을 Presenter 에 알림
    • View Control : 뷰의 UI 생성,변경,삭제 등의 방법을 Presenter 에 제공
  • Interactor
    • API 로부터 data 를 받아 entity(model) 을 생성함
  • Presenter
    • Interactor 에서 data 를 요청하고 받음
    • view logic 을 갖고 있으며 Interactor 에서
      data 를 받으면 View 에 어떻게 그릴지 알려줌
    • Life Cycle에 대한 처리
  • Entity
    • Interactor 에 의해 만들어지는 Model
    • 네트워크, DB 등의 데이터 모델
    • Realm Object, NSUserDefaults, Json Data 등
  • Router
    • Navigation logic 을 담당하며 screen 에서 다른 screen 으로 화면이
      변경되는 부분을 처리함
    • VIPER 컴포넌트들의 DI 를 담당함
  • 특징
    • Distribution : 4개의 구조 중 제일 분배가 잘 되어있음
    • Testability : 테스트에 용이함
    • Hard to use : 코드 양이 제일 많음. 작은 기능에도 많은 클래스를 작성해야함
공유하기