디자인패턴)MVP

소정·2023년 5월 26일

Kotlin

목록 보기
18/40

  • 규격화(인터페이스)에 촛점을 맞춘 아이
  • presenter 은 인터페이스를 사용해 만든다!!

💡 MVP란?

view와 model 완전 분리 특징이 가장 두드러짐 [뷰와 프레젠터가 해야할 작업을 이미 인터페이스로 규격화 한 것이 특징. 모듈화된 작업 템플릿을 만들때 용이한 구조임

1) Model : MVC패턴의 모델과 같은 역할 [데이터 취급 : Item, Person 등]
2) View : 사용자가 볼 화면 및 이벤트 처리 [액티비티, 프래그먼트]
3) Presenter : 뷰와 모델의 중계 역할. 컨트롤러가 비슷하지만 인터페이스로 역할을 정해놓는 특징이 있음


화면 xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".view.MainActivity">

    <EditText
        android:id="@+id/et_name"
        android:hint="이름"
        android:inputType="text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <EditText
        android:id="@+id/et_email"
        android:hint="이메일"
        android:inputType="textEmailAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_save"
        android:text="save Data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_load"
        android:text="load Data"
        android:backgroundTint="@color/teal_700"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"/>

    <TextView
        android:id="@+id/tv_result"
        android:text="result"
        android:textStyle="bold"
        android:textColor="@color/black"
        android:padding="8dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

[1] Model

UserVO.kt

package com.bsj0420.mvp.model

//이름 이메일 데이터 저장하는 역할의 데이터 클래스
data class UserVO(var name:String, var email:String)

UserModel.kt

package com.bsj0420.mvp.model

import android.content.Context
import androidx.core.content.edit

// 유저 정보(데이터)를 제어하는 기능 클래스
class UserModel constructor(val context: Context) { //데이터를 제어하기 위해서 콘텍스트 능력이 필요한 경우가 있다면 
    // 주 생성자로 전달 - 생성자 DI
    
    // 1) 데이터를 전달 받아서 SharedPreferences에 데이터 저장하는 기능
    fun saveData(name:String, email:String){

        val pref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
        pref.edit {
            putString("name", name)
            putString("email", email)
        }

    }
    
    // 2) SharedPreferences에서 데이터를 읽어와서 내보내는(return) 기능
    fun loadData(): UserVO {
        val pref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
        val name : String = pref.getString("name", "") as String //스트링으로 형변환
        val email : String =  pref.getString("email", "") as String

        return UserVO(name, email) //코틀린은 리턴 하나밖에 못함 그래서 모델을 사용하여 리턴
    }
    
}

[2] View

1. Presenter에서 정해놓은 view 규격을 메인에서 상속받음

  • 뷰는 프레젠터만 참조하면 됨!!

순서

  1. 프레젠터에서 뷰에서 할 일 정해둠
  2. view와 Presenter 참조 변수 만들기
    => view에서는 화면을 사용자가 보여주는 작업과 presenter만 필요해서 2개 참조변수 만듦
  3. presenter 객체 생성 및 초기화
  4. view binding
  5. view로서 사용자 이벤트 처리 - 프레젠터를 통해서
  6. 기능 채우기
package com.bsj0420.mvp.view

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.bsj0420.mvp.databinding.ActivityMainBinding
import com.bsj0420.mvp.model.UserVO
import com.bsj0420.mvp.presenter.MainContract
import com.bsj0420.mvp.presenter.MainPresenter

class MainActivity : AppCompatActivity(), MainContract.View {
    
    // 2. MVP 패턴 [Model - View - Presenter]
    // view와 model 완전 분리 특징이 가장 두드러짐 [뷰와 프레젠터가  해야할 작업을 이미 인터페이스로 규격화 한 것이 특징. 모듈화된 작업 템플릿을 만들때 용이한 구조임]
    // 1) Model : MVC패턴의 모델과 같은 역할 [데이터 취급 : Item, Person 등]
    // 2) View : 사용자가 볼 화면 및 이벤트 처리 [액티비티, 프래그먼트]
    // 3) Presenter : 뷰와 모델의 중계 역할. 컨트롤러가 비슷하지만 인터페이스로 역할을 정해놓는 특징이 있음


    //주요 특징
    // view와 프레젠터가 해야 할 작업들을 미리 interface를 통해서 규격화


    // # view 참조 변수 만들기
    lateinit var binding : ActivityMainBinding

    // # Presenter 참조변수
    lateinit var presenter : MainPresenter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // # 프레젠터 객체 생성 및 초기화
        presenter = MainPresenter()
        presenter.initial(this)

        // # view 로서의 역할
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // # view로서 사용자 이벤트 처리 - 프레젠터를 통해서
        binding.btnSave.setOnClickListener { presenter.clickSave(binding.etName.text.toString(), binding.etEmail.text.toString()) }
        
        binding.btnLoad.setOnClickListener { presenter.clickLoad() }

    }

    // 기능 채우기
    override fun showData(userVO: UserVO) {
        binding.tvResult.text = "${userVO.name} : ${userVO.email}"
    }

    override fun getContext(): Context {
        return this
    }
}

[3] Presenter

  • 인터페이스로 제작

1. 뷰와 프래젠터 class에서 할 일을 기술해 둔다!

-> 인터페이스로 기술해 둔다

package com.bsj0420.mvp.presenter

import android.content.Context
import com.bsj0420.mvp.model.UserVO

//View와 presenter 역할을 하는 클래스들이 가지고 있어야할 기능을 정하는 인터페이스
interface MainContract {

    // # view 역할을 하는 클래스가 가져야할 기능을 기술한 인터페이스
    interface View {
        //반드시 있어야할 기능 (추상메소드)
        //1) 모델의 데이터를 화면에 보여주는 기능
        fun showData(userVO: UserVO)
        //2) presenter에서 사용할 수 있는 콘텍스트를 리턴해주는 기능 필요!
        fun getContext() : Context
    }

    // # presenter 역할을 하는 클래스가 가져야할 기능을 기술한 인터페이스
    interface Presenter {
        //사용자의 이벤트에 따라 처리할 기능들을 만든다 (2개 - 버튼 2개니까)
        // view 역할 클래스의 요청에 의해 실행 될 메소드
        // 1) save 버튼 클릭 했을 때
        fun clickSave(name : String, email : String)

        // 2) load 버튼 눌렀을 때
        fun clickLoad()
        
    }

}

2. 인타페이스에 적어둔 일을 할 클래스는 만들고 상속 받는다!

1. 프레젠터 클래스에서 뷰와 모델이 참조변수를 만든다!!

package com.bsj0420.mvp.presenter

import com.bsj0420.mvp.model.UserModel

// 라면 가져야할 기능을 기술한 인터페이스를 구현하려 실제 기능을 작성
class MainPresenter : MainContract.Presenter{

    //프레젠터는 뷰와 모델을 연결해야하기 때문에
    //각각을 참조변수를 멤버로 보유한다
    //    var view : MainActivity //강한 결합은 x

    var view : MainContract.View?= null //1. 뷰 역할을 수행하는 클래스는 반드시 MainContract.View 인터페이스를 구현하고 있기에
    // 상속을 느슨하게 하기 위해 메인을 직접 부르지 않음

    var model : UserModel ?= null //2. 모델 역할을 수행하는 클래스 참조변수
    
    override fun clickSave(name: String, email: String) {
        TODO("Not yet implemented")
    }

    override fun clickLoad() {
        TODO("Not yet implemented")
    }
}

2. 생성자 생성, Presenter가 연결한 2개의 참조변수를 생성 및 전달받는 메소드 정의

package com.bsj0420.mvp.presenter

import com.bsj0420.mvp.model.UserModel

// 라면 가져야할 기능을 기술한 인터페이스를 구현하려 실제 기능을 작성
class MainPresenter : MainContract.Presenter{

    //프레젠터는 뷰와 모델을 연결해야하기 때문에
    //각각을 참조변수를 멤버로 보유한다
    //    var view : MainActivity //강한 결합은 x

    var view : MainContract.View?= null //1. 뷰 역할을 수행하는 클래스는 반드시 MainContract.View 인터페이스를 구현하고 있기에
    // 상속을 느슨하게 하기 위해 메인을 직접 부르지 않음

    var model : UserModel ?= null //2. 모델 역할을 수행하는 클래스 참조변수

    //Presenter가 연결한 2개의 참조변수를 생성 및 전달받는 메소드 정의
    //생성자 생성!
    fun initial (view : MainContract.View){
        this.view = view
        model = UserModel(view.getContext())
    }
    
    override fun clickSave(name: String, email: String) {
        TODO("Not yet implemented")
    }

    override fun clickLoad() {
        TODO("Not yet implemented")
    }
}

3. 기능

기능 처리 후 view에게 다시 돌려줘야함

package com.bsj0420.mvp.presenter

import com.bsj0420.mvp.model.UserModel
import com.bsj0420.mvp.model.UserVO


//뷰한테 어떤 동작을 하는지 얘기 듣고 처리 후 뷰에게 다시 요청

// 라면 가져야할 기능을 기술한 인터페이스를 구현하려 실제 기능을 작성
class MainPresenter : MainContract.Presenter{

    //프레젠터는 뷰와 모델을 연결해야하기 때문에
    //각각을 참조변수를 멤버로 보유한다
    //    var view : MainActivity //강한 결합은 x

    var view : MainContract.View?= null //1. 뷰 역할을 수행하는 클래스는 반드시 MainContract.View 인터페이스를 구현하고 있기에
    // 상속을 느슨하게 하기 위해 메인을 직접 부르지 않음

    var model : UserModel ?= null //2. 모델 역할을 수행하는 클래스 참조변수

    //Presenter가 연결한 2개의 참조변수를 생성 및 전달받는 메소드 정의
    //생성자 생성!
    fun initial (view : MainContract.View){
        this.view = view
        model = UserModel(view.getContext())
    }

    // 뷰의 save 버튼 클릭 이벤트를 대신 처리해 주는 기능 메소드
    override fun clickSave(name: String, email: String) {
        //모델에서 저장 요청
        model?.saveData(name, email)
    }

    override fun clickLoad() {
        //모델에게 데이터 요청
        var user : UserVO? = model?.loadData()
        
        //view 에게 데이터 출력 요청
        user?.let { 
            view?.showData(it)
        }
        
    }
}



장단점

장점

  1. MVC 처럼 데이터를 제어하는 코드가 Activity/Fragment 클래스 안에 없어서 간결함
  2. MVC보다 조금 더 명확하게 각 역할별 코드가 잘 분리되어 작성됨
  3. 각 역할이 인터페이스로 규격화되어 있어서 유지보수나 인수인계가 용이함
    4 View 안에서 model을 참조하고 있지 않기에 model의 변화에 영향받지 않음

단점

  1. MVC보다 만들어야 할 기본 파일들이 많아서 구조가 더 복잡해 보임
  2. view와 presenter 가 1:1로 대응 돼서 만들어짐, 파일이 엄청 많아짐
    ex ) LoginView , loginContract, loginPresenter 세개 한쌍
  3. 규모가 커지면 결국 presanter가 해야할 작업이 많아서 결국 비대해짐
profile
보조기억장치

0개의 댓글