Skip to content

Latest commit

ย 

History

History
235 lines (189 loc) ยท 8.89 KB

Android-MVP.md

File metadata and controls

235 lines (189 loc) ยท 8.89 KB

MVP, ์‹ค์ œ๋กœ ์ ์šฉํ•ด๋ณด์ž!

MVP๋ฅผ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—๋„ ์ ์šฉํ•ด๋ด…์‹œ๋‹น~~

์ž‘์„ฑ์ž : ์ด์ข…ํ˜„

Present Time : 2018โ€“09-14-FRI


0. ๊ฐœ์š”

0-1. ๊ธฐ์กด์˜ ๊ตฌ์กฐ, MVC

์šฐ๋ฆฌ๋Š” ์‹ค์งˆ์ ์œผ๋กœ ๋””์ž์ธ ํŒจํ„ด์„ ์ ์šฉํ•˜๊ธฐ ์ „์—๋Š”, MVC ํŒจํ„ด์— ๊ฐ€๊นŒ์šด ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
๊ฐ ์š”์†Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

  • Model: ๋ฐ์ดํ„ฐ, ์ƒํƒœ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹น
  • View: UI layout, Model๋กœ ๋ถ€ํ„ฐ ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์คŒ
  • Controller: ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ์˜ ์ž…๋ ฅ ์ˆ˜์‹ , View์˜ ์ด๋ฒคํŠธ์— ์•Œ๋ฆผ์„ ๋ฐ›๋Š” ์—ญํ• 
    MVC ๊ตฌ์กฐ ์‚ฌ์ง„ ๊ทธ๋ฆผ์œผ๋กœ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋Š” View์™€ Controller๊ฐ€ ๋ชจ๋‘ ์•กํ‹ฐ๋น„ํ‹ฐ, ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ™์€ ๋ทฐ์— ์ข…์†๋˜์–ด์žˆ๋‹ค๋Š” ํŠน์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ทฐ์—์„œ ์ปจํŠธ๋กค๋Ÿฌ์˜ ์—ญํ• ๊นŒ์ง€ ๋ชจ๋‘ ๋‹ด๋‹นํ•˜๊ธฐ์—, View๊ฐ€ ๋„ˆ๋ฌด ๊ฑฐ๋Œ€ํ•ด์ง€๊ณ , ์•ˆ๋“œ๋กœ์ด๋“œ API์— ์ข…์†๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ํž˜๋“ค๋‹ค๋Š” ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

0-2. MVP๋ž€?

์œ„์™€ ๊ฐ™์€ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ƒˆ๋กœ์šด ๋””์ž์ธ ํŒจํ„ด์ธ MVP ํŒจํ„ด์ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.
์‹ค์ œ๋กœ MVP ํŒจํ„ด์„ ์ ์šฉํ•˜๊ธฐ ์ „์—, MVP ํŒจํ„ด์˜ ๊ฐœ๋…์„ ์•Œ์•„๋ด…์‹œ๋‹ค.

MVP๋Š” Model, View, Presenter๋ผ๋Š” ์„ธ๊ฐœ์˜ ์š”์†Œ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค.
์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ๊ฐ ์š”์†Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

  • Model: ๋ฐ์ดํ„ฐ, ์ƒํƒœ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹น
  • View: xml, View(Activity, Fragment)๊ฐ€ View์— ํ•ด๋‹นํ•˜๋ฉฐ, Presenter์— ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • Presenter: View๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์˜จ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , Model์„ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค. MVP ์˜ˆ์‹œ ์‚ฌ์ง„ MVP ํŒจํ„ด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.
  1. View์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  2. View๋Š” Presenter์— ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  3. Presnter์—์„œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ด ๋•Œ, Model์„ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜, Model์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. Presenter์—์„œ View๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

0-3. MVP, ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผํ• ๊นŒ?

๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฌธ์„œ ์— ๋”ฐ๋ผ์„œ MVP ํŒจํ„ด์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„์„ ํ•ด์•ผํ• ์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค!

Contract

๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•œ ์˜ˆ์ œ์—์„œ๋Š” Contract ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด, MVP ํŒจํ„ด์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

interface TaskDetailContract {

    interface View : BaseView<Presenter> {

    }

    interface Presenter : BasePresenter {
    }
}

Contract์—์„œ๋Š” View ์ธํ„ฐํŽ˜์ด์Šค์™€, Presenter ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
BaseView, BasePresenter์—๋Š” ๋ชจ๋“  ๋ทฐ์™€ ํ”„๋ ˆ์  ํ„ฐ์— ๊ณตํ†ต์ ์œผ๋กœ ๋“ค์–ด๊ฐˆ ๋ถ€๋ถ„์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

  • View ์ธํ„ฐํŽ˜์ด์Šค๋Š” Activity ํ˜น์€ Fragment, ์ฆ‰ ๋ทฐ์— ์ƒ์†์ด ๋ฉ๋‹ˆ๋‹ค.
  • View ์ธํ„ฐํŽ˜์ด์Šค๋Š” Presenter์—์„œ View๋ฅผ ์ปจํŠธ๋กคํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • Contract๋ฅผ ์ •์˜ํ•จ์œผ๋กœ์จ, ์šฐ๋ฆฌ๋Š” Contract๋งŒ ๋ณด๊ณ  ๋Œ€๋žต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ, ์ „๋ฐ˜์ ์ธ ๊ตฌ์กฐ๋ฅผ ์•Œ๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•ด๋ด…์‹œ๋‹น~~

๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

๋‘ ๊ฐœ์˜ EditText์—์„œ ํ…์ŠคํŠธ๋ฅผ ๋ฐ›์•„์™€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ด์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ฉฐ, MVP ํŒจํ„ด์ด ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•ด๋ด…์‹œ๋‹ค!

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        plus.setOnClickListener {
            result.text = calc(firstNum.toInt(), secondNum.toInt(), '+').toString()
        }

        minus.setOnClickListener {
            result.text = calc(firstNum.toInt(), secondNum.toInt(), '-').toString()
        }

        divide.setOnClickListener {
            result.text = calc(firstNum.toInt(), secondNum.toInt(), '/').toString()
        }

        multiple.setOnClickListener {
            result.text = calc(firstNum.toInt(), secondNum.toInt(), '*').toString()
        }

    }

    fun calc(x: Int, y: Int, type: Char) = when (type) {
        '+' -> x + y
        '-' -> x - y
        '*' -> x * y
        '/' -> x / y
        else -> 0
    }

๊ธฐ์กด ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. plus, minus, divide, multiple์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  2. ๋‘ ๊ฐœ์˜ EditText์˜ ํ…์ŠคํŠธ๋ฅผ ๋ฐ›์•„์™€ calc ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ฒฐ๊ณผ๋ฅผ result ๋ผ๋Š” TextView์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์‹ค ๋ฐ”๊พธ๋“  ์•ˆ๋ฐ”๊พธ๋“  ๋ณ„๋กœ ๋‹ค๋ฅผ๊ฒŒ ์—†์„๊ฑฐ๊ฐ™์€๋ฐ ์•„๋ฌดํŠผ MVP๋กœ ๋ฐ”๊ฟ”๋ด…์‹œ๋‹ค~~

1. Contract ์ •์˜ํ•˜๊ธฐ

interface MainContract {
    interface View {
        fun setCalcResult(res: Int)
        fun getFirstNum(): Int
        fun getSecondNum(): Int
    }

    interface Presenter {
        fun calc(x: Int, y: Int, type: Char)
    }
}

2. View ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ํ•˜๊ธฐ

View(Activity, Fragment)์— View ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

class MainActivity : AppCompatActivity(), MainContract.View {
    val presenter by lazy { MainPresenter(this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun setCalcResult(res: Int) { result.text = res.toString() }

    override fun getFirstNum(): Int = firstNum.toInt()

    override fun getSecondNum(): Int = secondNum.toInt()
}

3. Presenter ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ํ•˜๊ธฐ

MainPresenter ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“  ํ›„, MainContract.Presenter๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

class MainPresenter(val view: MainContract.View) : MainContract.Presenter {
    override fun calc(x: Int, y: Int, type: Char) {
        val res = when (type) {
            '+' -> x + y
            '-' -> x - y
            '*' -> x * y
            '/' -> x / y
            else -> 0
        }
        view.setCalcResult(res)
    }
}

ํ”„๋ ˆ์  ํ„ฐ๋ฅผ ๋งŒ๋“ค ๋•Œ, View๋ฅผ ์ƒ์„ฑ์ž๋กœ ๋ฐ›์Œ์œผ๋กœ์จ, ํ”„๋ ˆ์  ํ„ฐ์—์„œ ๊ฐ„์ ‘์ ์œผ๋กœ View๋ฅผ ์กฐ์ข…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ์•กํ‹ฐ๋น„ํ‹ฐ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ, ๋‚จ์€ ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ๊ตฌํ˜„ํ•ฉ์‹œ๋‹ค.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        plus.setOnClickListener {
            presenter.calc(getFirstNum(), getSecondNum(), '+')
        }

        minus.setOnClickListener {
            presenter.calc(getFirstNum(), getSecondNum(), '-')
        }

        divide.setOnClickListener {
            presenter.calc(getFirstNum(), getSecondNum(), '/')
        }

        multiple.setOnClickListener {
            presenter.calc(getFirstNum(), getSecondNum(), '*')
        }
    }

MVC ๊ตฌ์กฐ์˜€๋‹ค๋ฉด ์›๋ž˜ Activity ์•ˆ์— ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋กœ์ง์ด ์žˆ์—ˆ์ง€๋งŒ, Presenter๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‚ด Presenter์—์„œ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ณ , View๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ธฐ

0. Mockito๋ž€?

Mockito๋Š” JUnit ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” Mocking, Verification์„ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

testImplementation 'org.mockito:mockito-core:2.22.0'

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ชจ๋“ˆ ๋‹จ์œ„ build.gradle์— ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Mock์ด๋ž€?

Mock์ด๋ž€ ๊ฐ€์งœ ๊ฐ์ฒด๋กœ, ํ…Œ์ŠคํŠธ ํ•  ๋•Œ Mock ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ํ–‰์œ„๋ฅผ ๊ฒ€์ฆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” View ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ mockingํ•ด View์˜ ํ–‰์œ„๋ฅผ ๊ฒ€์ฆํ•  ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

1. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ž‘์„ฑํ•˜๊ธฐ

class MainPresenterTest {

    lateinit var presenter: MainPresenter
    lateinit var view: MainContract.View

    @Before
    fun setUp() {
        view = Mockito.mock(MainContract.View::class.java)
        presenter = MainPresenter(view)
    }

    @Test
    fun plusTest(){
        presenter.calc(3,5,'*')
        Mockito.verify(view).setCalcResult(15)
    }
}

@Before

Before ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์†Œ๋“œ๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์—, ๋ฌด์กฐ๊ฑด ์‹คํ–‰๋˜๊ณ  ๋„˜์–ด๊ฐ€๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.
setUp() ๋ฉ”์†Œ๋“œ์—์„œ view๋ฅผ mocking ์‹œํ‚ค๊ณ , presenter๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

@Test

  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ๋Š” Presenter์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • calc ๋ฉ”์†Œ๋“œ๋Š” ๊ณ„์‚ฐ์„ ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ setCalcResult ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ทฐ์— ์ถœ๋ ฅ์‹œ์ผœ์ฃผ๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.
  • Mockito์˜ verify() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ, setCalcResult(15)๊ฐ€ ๋™์ž‘๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ  ๋ฌธ์„œ

https://thdev.tech/androiddev/2016/10/23/Android-MVC-Architecture/
https://medium.com/nspoons/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-architecture-%ED%8C%A8%ED%84%B4-part-1-%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-model-view-controller-881c6fda24d9
https://upday.github.io/blog/model-view-presenter/
https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/