[Android] 안드로이드 MVP 패턴 알아보기 + 실습

이황근·2024년 7월 7일

Android

목록 보기
6/9

앞서 알아봤던 MVC 패턴과 다른 아키텍처 패턴에는 MVP(Model-View-Presenter)가 있습니다. MVC와 어떤 점이 유사하고 어떤 차이점이 있는지를 비교하면서 글을 작성해보도록 하겠습니다.

MVP (Model - View - Presenter)

Model

MVC에서의 Model과 똑같은 개념이기 때문에 궁금하신 분들은 이전 글 참고하시면 좋을 것 같습니다.

View

MVC에서와 마찬가지로 UI부분을 담당합니다. 하지만 Android에서는 Activity와 Fragment는 View로 분류되어 View의 역할을 하면서도 UI 갱신과 같은 작업을 Activity, Fragment에서 처리하기 때문에 Controller, View가 합쳐진 모호한 구조라고 할 수 있습니다.
Controller의 역할을 살펴 보면 크게 2가지로 설명할 수 있습니다.
1. UI 업데이트
2. Model과의 상호작용

이제 MVP에서는 Presenter가 UI업데이트를 처리하고 Model과의 상호작용을 하는 Bridge 역할을 Presenter가 하기 때문에 분리 되었다고 볼 수 있습니다.

하지만 이렇게 분리를 해도 View와 Presenter 간의 의존성이 생기는 것을 볼 수 있는데 이때 Presenter와 View가 직접적으로 연결되지 않도록 View Interface를 만들고 Activity가 implement하여 이 Interface를 이용하면 가상의 View Interface가 생성되어 Presenter Logic Test를 자유롭게 할 수 있습니다.

Presenter

MVC의 Controller와 유사한 역할을 담당합니다. Model과 상호작용하고 View UI 갱신을 요청하는 크게 2가지 역할을 합니다. 다만 Android MVC에서 직접 UI를 갱신하는 것이 아니라 어떤 것을 보여줘야 하는지에 대한 정보만 View에 전달하는 것입니다. 그래서 '요청'은 Presenter가 하지만 'UI갱신'은 View가 하게 되는 것입니다.
그래서 여기서 Controller와 Presenter의 차이점이 존재하는 것을 알 수 있습니다. Presenter는 Controller와 다르게 View와 직접적으로 연결되는 것이 아니라 Bridge 역할만 한다는 것입니다.
하지만 View, Model간의 의존성은 여전히 남아있습니다.

의존성

Model - View, Presenter와 상관 없이 그저 Input이 들어 왔을 때 Output을 내주거나 내부에서 행위만 존재하기 떄문에 의존성이 아예 없다고 할 수 있습니다.
View - 사용자 입력(Event)를 받아서 처리하는 과정에서 Presenter에 전송하고 Presenter로 부터 데이터를 받아 UI를 업데이트 하기 때문에 Presenter와의 의존성이 있다고 볼 수 있습니다.
Presenter - 과정에서 View에서의 요청이 있고 그리고 그에 맞게 Model에서 데이터를 가져와 View에 전달한다는 점에서 의존성이 있다고 볼 수 있습니다.

핵심 포인트

  1. View-Presenter / Presenter-Model 간의 통신은 전부 Interface를 통해 이루어집니다.
  2. 하나의 Presenter Class는 하나의 View를 관리합니다.
  3. Model과 View는 연결 관계가 아예 없습니다.

Interface란?

인터페이스는 클래스가 따라야 할 기본적인 구조나 틀을 정의합니다. 이는 클래스가 어떤 기능을 제공해야 하는지를 명시하고, 해당 기능을 구현하는 방법은 클래스에 위임합니다.
Kotlin에서의 Interface에 대한 내용은 다른 글에서 다루도록 하곘습니다.

Code

Interface

View, Model,Presenter에 대한 Interface를 설정하여 추후에 기존 MVC 패턴에서 있던 의존성을 제거하기 위해 Abstract Method를 포함해줍니다.

interface Contract {
    interface View {
        // method to display progress bar
        // when next random course details
        // is being fetched
        fun showProgress()

        // method to hide progress bar
        // when next random course details
        // is being fetched
        fun hideProgress()

        // method to set random
        // text on the TextView
        fun setString(string: String?)
    }

    interface Model {
        // nested interface to be
        interface OnFinishedListener {
            // function to be called
            // once the Handler of Model class
            // completes its execution
            fun onFinished(string: String?)
        }

        fun getNextCourse(onFinishedListener: OnFinishedListener?)
    }

    interface Presenter {
        // method to be called when
        // the button is clicked
        fun onButtonClick()

        // method to destroy
        // lifecycle of MainActivity
        fun onDestroy()
    }
}

Model

이전에 Contract에서 작성 해두었던 Method를 Implement 해주고 사용자가 버튼을 클릭 했을 때 작동하는 GetNextCource 메소드를 선언해줍니다.
getNextCourse에서는 1200ms 후에
onFinishedListener!!.onFinished(getRandomString)를 실행시킵니다. 이를 통해 Progess가 보여지고 나서 끝날 때 String이 나타나는 것을 구현할 수 있습니다.
그리고 getRandomString 프로퍼티에서는 Model의 Random String을 보여줍니다.

class Model : Contract.Model {
    // array list of strings from which
    // random strings will be selected
    // to display in the activity
    private val arrayList =
        Arrays.asList(
            "Text1",
            "Text2",
            "Text3",
            "Text4"
        )

    // this method will invoke when
    // user clicks on the button
    // and it will take a delay of
    // 1200 milliseconds to display next course detail
    override fun getNextCourse(onFinishedListener: Contract.Model.OnFinishedListener?) {
        Handler().postDelayed({ onFinishedListener!!.onFinished(getRandomString) }, 1200)
    }


    // method to select random
    // string from the list of strings
    private val getRandomString: String
        private get() {
            val random = Random()
            val index = random.nextInt(arrayList.size)
            return arrayList[index]
        }
}

View

Presenter를 이용해서 어떤 행위을 할지 선언을 해줍니다.

	// instantiating object of Presenter Interface 
    presenter = Presenter(this, Model()) 
    
    // method to display the Course Detail TextView 
    override fun showProgress() { 
        progressBar!!.visibility = View.VISIBLE 
        textView!!.visibility = View.INVISIBLE 
    } 
  
    // method to hide the Course Detail TextView 
    override fun hideProgress() { 
        progressBar!!.visibility = View.GONE 
        textView!!.visibility = View.VISIBLE 
    } 
  
    // method to set random string 
    // in the Course Detail TextView 
    override fun setString(string: String?) { 
        textView!!.text = string 
    } 

Presenter

Model Interface와 MainView의 Interface를 선언을 해주고 여기서 어떤 작업이 이루어질지 선언을 해줍니다.

class Presenter(
    private var mainView: Contract.View?,
    private val model: Contract.Model) : Contract.Presenter,
    Contract.Model.OnFinishedListener {

    // operations to be performed
    // on button click
    override fun onButtonClick() {
        if (mainView != null) {
            mainView!!.showProgress()
        }
        model.getNextCourse(this)
    }

    override fun onDestroy() {
        mainView = null
    }

    // method to return the string
    // which will be displayed in the
    // Course Detail TextView
    override fun onFinished(string: String?) {
        if (mainView != null) {
            mainView!!.setString(string)
            mainView!!.hideProgress()
        }
    }

}

장점

View Interface를구현했기 때문에 가상의 View를 이용하거나 다른 View를 사용하여 Presenter 로직 테스트를 하기 쉽다는 장점이 있다.

어려운 점

Callback, Handler, Lambda 식이 추가가 된 예제를 가져와서 설명을 하였는데 이 때문에 MVP에 대한 이해가 어려웠다. 좀 더 쉬운 예제로 확인을 하면 더 쉽게 이해할 수 있을 것이다.

참고 : https://brunch.co.kr/@mystoryg/171
https://www.geeksforgeeks.org/mvp-model-view-presenter-architecture-pattern-in-android-with-example/?ref=next_article

profile
낭만 개발자

0개의 댓글