MVP๋ฅผ ์ค์ ํ๋ก์ ํธ์๋ ์ ์ฉํด๋ด ์๋น~~
์์ฑ์ : ์ด์ข ํ
Present Time : 2018โ09-14-FRI
์ฐ๋ฆฌ๋ ์ค์ง์ ์ผ๋ก ๋์์ธ ํจํด์ ์ ์ฉํ๊ธฐ ์ ์๋, MVC ํจํด์ ๊ฐ๊น์ด ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค.
๊ฐ ์์๋ ์๋์ ๊ฐ์ ๋ถ๋ถ์ ๋ด๋นํฉ๋๋ค.
- Model: ๋ฐ์ดํฐ, ์ํ, ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋น
- View: UI layout, Model๋ก ๋ถํฐ ์ค๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค
- Controller: ์ฌ์ฉ์๋ก๋ถํฐ์ ์
๋ ฅ ์์ , View์ ์ด๋ฒคํธ์ ์๋ฆผ์ ๋ฐ๋ ์ญํ
๊ทธ๋ฆผ์ผ๋ก ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์๋๋ก์ด๋์์๋View
์Controller
๊ฐ ๋ชจ๋ ์กํฐ๋นํฐ, ํ๋๊ทธ๋จผํธ๊ฐ์ ๋ทฐ์ ์ข ์๋์ด์๋ค๋ ํน์ฑ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
๋ทฐ์์ ์ปจํธ๋กค๋ฌ์ ์ญํ ๊น์ง ๋ชจ๋ ๋ด๋นํ๊ธฐ์, View๊ฐ ๋๋ฌด ๊ฑฐ๋ํด์ง๊ณ , ์๋๋ก์ด๋ API์ ์ข ์๋์ด ์๊ธฐ ๋๋ฌธ์ ํ ์คํธ๊ฐ ํ๋ค๋ค๋ ๋จ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
์์ ๊ฐ์ ๋จ์ ์ ๋ณด์ํ๊ธฐ ์ํด์ ์๋ก์ด ๋์์ธ ํจํด์ธ MVP ํจํด์ด ๋ฑ์ฅํ์ต๋๋ค.
์ค์ ๋ก MVP ํจํด์ ์ ์ฉํ๊ธฐ ์ ์, MVP ํจํด์ ๊ฐ๋
์ ์์๋ด
์๋ค.
MVP๋ Model, View, Presenter๋ผ๋ ์ธ๊ฐ์ ์์๋ก ์ด๋ฃจ์ด์ ธ ์์ต๋๋ค.
์๋๋ก์ด๋์์ ๊ฐ ์์๋ ์๋์ ๊ฐ์ ๋ถ๋ถ์ ๋ด๋นํฉ๋๋ค.
- Model: ๋ฐ์ดํฐ, ์ํ, ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋น
- View: xml, View(Activity, Fragment)๊ฐ View์ ํด๋นํ๋ฉฐ, Presenter์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํฉ๋๋ค.
- Presenter: View๋ก๋ถํฐ ๋ฐ์์จ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ , Model์ ์
๋ฐ์ดํธ ํฉ๋๋ค.
MVP ํจํด์ ๋ค์๊ณผ ๊ฐ์ด ์งํ๋ฉ๋๋ค.
- View์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํฉ๋๋ค.
- View๋ Presenter์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํฉ๋๋ค.
- Presnter์์ ๋ก์ง์ ์ฒ๋ฆฌํฉ๋๋ค.
- ์ด ๋, Model์ ์ ๋ฐ์ดํธํ๊ฑฐ๋, Model์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
- Presenter์์ View๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
๊ตฌ๊ธ์์ ์ ๊ณตํ๋ ๋ฌธ์ ์ ๋ฐ๋ผ์ MVP ํจํด์ ์ด๋ป๊ฒ ๊ตฌํ์ ํด์ผํ ์ง ์์๋ด ์๋ค!
๊ตฌ๊ธ์์ ์ ๊ณตํ ์์ ์์๋ 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
}
๊ธฐ์กด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
plus
,minus
,divide
,multiple
์ ํด๋ฆญํฉ๋๋ค.- ๋ ๊ฐ์ EditText์ ํ
์คํธ๋ฅผ ๋ฐ์์
calc
ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. - ๊ฒฐ๊ณผ๋ฅผ result ๋ผ๋ TextView์ ์ถ๋ ฅํฉ๋๋ค.
์ฌ์ค ๋ฐ๊พธ๋ ์๋ฐ๊พธ๋ ๋ณ๋ก ๋ค๋ฅผ๊ฒ ์์๊ฑฐ๊ฐ์๋ฐ ์๋ฌดํผ MVP๋ก ๋ฐ๊ฟ๋ด ์๋ค~~
interface MainContract {
interface View {
fun setCalcResult(res: Int)
fun getFirstNum(): Int
fun getSecondNum(): Int
}
interface Presenter {
fun calc(x: Int, y: Int, type: Char)
}
}
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()
}
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๋ฅผ ์กฐ์ข
ํ ์ ์์ต๋๋ค.
์ด์ , ๋จ์ ์กํฐ๋นํฐ๋ฅผ ๊ตฌํํฉ์๋ค.
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๋ฅผ ์ ๋ฐ์ดํธํ๊ฒ ํ์ต๋๋ค.
Mockito๋ JUnit ์์์ ๋์ํ๋ Mocking, Verification์ ๋์์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
testImplementation 'org.mockito:mockito-core:2.22.0'
์์ ์ฝ๋๋ฅผ ๋ชจ๋ ๋จ์ build.gradle
์ ์ถ๊ฐํ์ฌ ์ฌ์ฉํฉ๋๋ค.
Mock์ด๋ ๊ฐ์ง ๊ฐ์ฒด๋ก, ํ ์คํธ ํ ๋ Mock ๊ฐ์ฒด๋ฅผ ํตํด ํ์๋ฅผ ๊ฒ์ฆ ํ ์ ์์ต๋๋ค.
์ด๋ฒ์๋ View ์ธํฐํ์ด์ค๋ฅผ mockingํด View์ ํ์๋ฅผ ๊ฒ์ฆํ ๊ฒ ์ ๋๋ค.
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
์ด๋
ธํ
์ด์
์ด ๋ถ์ ๋ฉ์๋๋ ํ
์คํธ ์ผ์ด์ค๊ฐ ์คํ๋๊ธฐ ์ ์, ๋ฌด์กฐ๊ฑด ์คํ๋๊ณ ๋์ด๊ฐ๋ ๋ฉ์๋์
๋๋ค.
setUp()
๋ฉ์๋์์ view๋ฅผ mocking ์ํค๊ณ , presenter๋ฅผ ์์ฑํด์ค๋๋ค.
- ํ ์คํธ ์ผ์ด์ค์์๋ 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/