7. 안드로이드 애플리케이션 설계 패턴 (MVC, MVP, MVVM)
- 일반적인 MVC, MVP, MVVM 디자인 패턴
7.1. MVC 디자인 패턴
- Model, View, Controller 로 관심사 분리
- 안드로이드 플랫폼 등장 초기에 자연스럽게 적용되기 시작
모델의 역할
- 애플리케이션의 비즈니스 로직, 사용 되는 데이터를 다룸
- 표현 형식에 의존적이지 않고, 사용자에게 보이지 않아 어떻게 보일지 신경쓰지 않아도됨
- 비즈니스 데이터 = DBMS에 의해서 관리
- 몇 함수를 통해서 데이터를 제공, 입력, 수정
- 안드로이드에서 데이터베이스의 Entity 를 담당하는 POJO 클래스를 포함한 SQLite, Room, Realm 등
[POJO 클래스 예시]
data class Employee(
var name: String, var id: String,
val salary: Double
)
뷰의 역할
- 사용자에게 보여지는 영역
- 모델로 부터 얻은 데이터를 뷰에서 나타냄
- 안드로이드에서 Activity, Fragment 가 그 예시
컨트롤러의 역할
- 모델과 뷰에 의존
- 뷰로 부터 입력 받거나, 특정 이벤트 발생 시 모델 또는 뷰를 변경
- 새로운 데이터가 입력되는 모델로 전달하여 데이터베이스에 저장하고 그 동시에 모델의 상태 변화를 감지하여 등록된 뷰에 상태를 업데이트 해서 보여줄 수 있는것.
- MVC 에서는 Activity 와 Fragment는 뷰의 역할도 하고 컨트롤러의 역할도 한다.
- 컨트롤러는 뷰와 모델에 의존
- 뷰는 모댈 상태 변화에 따라 능동적인 대응을 함
MVC 디자인 패턴의 장단점
- 직관적
- 규모가 적은 앱에 적용시 개발 기간 단축가능
- 모든 코드가 액티비티, 프래그먼트 같은 컨트롤러에 작성되는 경향 -> 코드 파악 편리
- 코드량 증가 시 스파케티 코드로 전락.. -> 시간 흐를 수록 유지보수 비용 증가!
- 컨트롤러가 뷰와 모델에 의존, 뷰는 모델에 의존. -> 결합도 높아..! -> 유닛 테스트 거의 불가능
7.2 MVP 디자인 패턴
- MVC 는 UI와 비즈니스 로직이 Activity 와 Fragment에 공존
- MVP 디자인 패턴은 비슷하지만 Activity 와 Fragment의 UI , 비즈니스 로직을 분리하는데 집중
- model 과 view 의 역할은 MPC 와 비슷허나
- controller 대시 presenter 라는 개념 사용
- UI 와 비즈니스 로직 분리 -> 유닛 테스트 가능
MVP 디자인 패턴 장점
- Presenter 는 View 와 Model의 인스턴스를 가지면서 이 둘을 연결해주는 역할.
- Presenter 와 View 는 1:1 관계를 갖음
- View model 간의 의존성이 없음 (장점)
- UI / 비즈니스 로직 분리 (유닛 테스트 수월)
- 하지만, View / Presenter 간의 의존성이 높고 1:1 관계를 유지해야 하므로 Presenter 를 재사용할 수 없음
- view 가 늘어날 때마다 Presenter 도 같이 늘어남. 클래스가 많아지게 된다...
- 앱의 기능이 추가될 수록 Presenter가 거대해짐...
MVP 디자인 패턴 구현하기
Contract Class 만들기
- 구성 요소의 혁할과 관계의 정의
- 구성 요소 = View, Presenter 정도
- Model = Contract 클래스에 포함 또는 Repository 패턴으로 따로 정의
class MainContract {
interface View {
fun showPersonList(personList: List<Person>)
fun notifyDataChanged()
}
interface Presenter {
fun load()
fun addPerson(person: Person)
fun removePerson(person: Person)
}
}
Presenter Class 만들기
- View 의 역할
- View 인터페이스에 정의된 매서드 재정의로 데이터를 화면에 뿌림
- Presenter 생명 주기 또는 Click event에 대한 내용 알림
- View 인터페이스는 Activity, Fragment 에 주로 구현
class MainPresenter(database: Database, view: MainContract.View) : Presenter {
private val database: Database
private val view: MainContract.View // 오로지 계약된 MainContract.Presenter 에만 참조 된다
override fun load() {
view.showPersonList(database.getPersonList())
}
override fun addPerson(person: Person) {
database.add(person)
}
override fun removePerson(person: Person) {
database.remove(person)
}
init {
this.database = database
this.view = view
this.database.setOnDatabaseListener(object : DatabaseListener() {
fun onChanged() {
this@MainPresenter.view.notifyDataChanged()
}
})
}
}
7.3 MVVM 디자인 패턴
- MVP 패턴애서는 Presenter가 view에 어떤 일을 요청하는지 명백히 확인이 가능했다
- 하지만, View 와 Presenter 가 강하게 결합되는 문제점이 있음
- MVVM 에서는 데이터 바인딩 및 LiveData 또는 RxJava 같은 Observable 타입을 이용하여 Presenter와 View 사이 의존력을 끊는데 집중한다.
- Presenter 대신 ViewModel 이라는 구성 요소를 사용!
- ViewModel
- View 에 표현할 데이터를 Observable 타입으로 관리
- View 들이 ViewModel 데이터를 구독! 요청하여 화면에 나타냄
- ViewModel : View = 1 : N 관계를 가진다! 😲
- ViewModel 이 View 에 느슨하게 연결되도록 Data Binding 라이브러리가 필수적으로 사용된다
- 생명 주기 or 유저 상호작용에 따른 ViewModel의 Model 데이터 요청
- Model로부터 받은 데이터를 가공해 관찰가능한(observable) 타입의 형태로 ViewModel에 저장
- View와 ViewModel은 Databinding이 이루어져야함
- 데이터 상태가 벼경되면 해당 데이터를 구독하는 view 들에 변경사항을 알리게됨. 그러면 뷰 업데이트
ViewModel 구현하기
- MainViewModel 은 데이터 바인딩 패키지의 BaseObservable을 상속.
- 데이터에 반응해서 뷰를 갱신할 수있도록 해준 것.
- 핵심은 MainViewModel이 Person 목록을 가지며, View 관련 코드를 참조하지 않는 다는 것!
[MainViewModel.kt]
class MainViewModel(database: Database) : BaseObservable() {
private val database: Database
private val items: MutableList<Person> = ArrayList<Person>()
fun load() {
items.clear()
items.addAll(database.getPersonList())
notifyChange()
}
fun addPerson(person: Person?) {
database.add(person)
}
fun removePerson(person: Person) {
database.remove(person)
}
fun getItems(): List<Person> {
return items
}
init {
this.database = database
this.database.setOnDatabaseListener(object : DatabaseListener() {
fun onChanged() {
load()
}
})
}
}
[MainActivity.kt]
class MainActivity : AppCompatActivity(), MainViewHolder.HolderClickListener {
var viewModel: MainViewModel? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
...
viewModel = MainViewModel(instance!!)
binding!!.viewModel = viewModel
viewModel!!.load()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel!!.addPerson(
Person(
System.currentTimeMillis(),
String.format("New Charles %d", Random().nextInt(1000))
)
)
return super.onOptionsItemSelected(item)
}
override fun onDeleteClick(person: Person) {
viewModel!!.removePerson(person)
}
...
}
[activity_main.kt]
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.sample.viewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:items="@{viewModel.items}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2021.07.16 - [Android/클린 아키텍처] - [Clean Architecture] 01-안드로이드 애플리케이션 설계 SOLID
2021.07.16 - [Android/클린 아키텍처] - [Clean Architecture] 02-안드로이드 클린 아키텍처
2021.07.17 - [Android/클린 아키텍처] - [Clean Architecture] 03-안드로이드 권장 애플리케이션 설계 원칙
해당 글은 '아키텍처를 알아야 앱 개발이 보인다' 를 공부하며 요약 정리한 글 입니다.
'Android > 클린 아키텍처' 카테고리의 다른 글
[Clean Architecture] 06-Dagger2 란? (+ Adnroid 적용 샘플) (0) | 2021.07.20 |
---|---|
[Clean Architecture] 05-의존성 주입(DI)과 그 필요성 (0) | 2021.07.19 |
[Clean Architecture] 03-안드로이드 권장 애플리케이션 설계 원칙 (2) | 2021.07.17 |
[Clean Architecture] 02-안드로이드 클린 아키텍처 (1) | 2021.07.16 |
[Clean Architecture] 01-안드로이드 애플리케이션 설계 SOLID (0) | 2021.07.16 |