티스토리 뷰

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 의 역할
    1. View 인터페이스에 정의된 매서드 재정의로 데이터를 화면에 뿌림
    2. 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-안드로이드 권장 애플리케이션 설계 원칙

해당 글은 '아키텍처를 알아야 앱 개발이 보인다' 를 공부하며 요약 정리한 글 입니다.
댓글
최근에 올라온 글
최근에 달린 댓글
네이버 이웃추가
«   2024/03   »
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 26 27 28 29 30
31
글 보관함