서두
MVC 패턴의 문제점은 Controller 와 Android Framework, View와 강력한 종속성 때문에 테스트 코드를 작성하기가 어렵다는 것이 였습니다.
위의 언급된 문제를 해결한 것이 MVP 패턴입니다. 이 포스트는 기존 MVC 패턴을 어떻게 MVP 패턴으로 변경하는 지에 대해서 알아보도록 하겠습니다.
실행 화면
</br>
앱에 대해 간단히 설명하자면 서버로 부터 HTML 문서를 다운받고 문서로 부터 이미지-제목을 파싱하여 RecyclerView에 뿌려주는 갤러리 앱입니다. 비동기 처리를 위해 저는 Coroutine을 사용했습니다.
실행화면은 MVC, MVP, MVVM 패턴 모두 동일합니다.
샘플 코드
아래 Repository를 참고바랍니다.
git checkout mvp
MVP
MVC 는 Model-View-Presenter의 약자로써 3개의 집합으로 구분합니다.
MVC와 큰 차이점은 Android Activity는 이제 View에 속하게 됩니다.
Presenter
View와 Model를 서로 이어주는 역할을 합니다. View와 Presenter는 미리 정의한 Interface를 통해서 서로 통신을 합니다.
기존 Controller와 큰 차이점은 Android Framework 와 종속성이 모두 제거가 되었습니다.
MainContract
class MainContract {
interface View {
fun addItem(imageData: ImageData)
fun updateCount(count : Int)
}
interface Presenter {
fun launch()
fun init()
fun deInit()
}
}
MainPresenter
class MainPresenter(private val view: MainContract.View) : MainContract.Presenter, CoroutineScope {
lateinit var job: Job
var count = 0
override fun init() {
job = Job()
count = 0
}
override fun deInit() {
job.cancel()
}
override fun launch() {
count = 0
launch {
println("Thread : " + Thread.currentThread().name)
val channel = ImageDataProvider().get(this)
channel.consumeEach {
count++
withContext(Main) {
view.onAddedItem(it)
view.onUpdateCounter(count)
}
}
}
}
override val coroutineContext: CoroutineContext
get() = job + newSingleThreadContext("Presenter")
}
MyPresenter 는 MainContract.Presenter 를 상속받아 구현을 하고 View 는 MainContract.Presenter interface를 통해서 이벤트를 전달합니다.
- init() : 코루틴 스코프를 활성화하고
counter
를 초기화합니다. - deInit() : 코루틴을 종료하고 코루틴 스코프를 정리 합니다. Acitivity가 더 이상 사용하지 않을 때 호출 할 것입니다.
- launch() : 네트워크 연산을 하고 Model를 업데이트하여 그 결과를 callback interface를 통해서 View에 전달합니다.
View
화면을 구성하는 컴포넌트입니다.
위 샘플코드에서 View는 Layout Xml, RecyclerView Adapter인 ImageDataAdapter 그리고 MainActicity가 새롭게 추가가 됩니다.
MainActivity
class MainActivity : AppCompatActivity(), CoroutineScope, MainContract.View {
...
private lateinit var presenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
job = Job()
presenter = MainPresenter(this).apply {
init()
launch()
}
viewManager = LinearLayoutManager(this)
viewAdapter = ImageDataAdapter(this)
recyclerView = this.findViewById<RecyclerView>(R.id.image_title_list).apply {
layoutManager = viewManager
adapter = viewAdapter
}
}
override fun onAddedItem(imageData: ImageData) {
launch {
viewAdapter.add(imageData)
}
}
override fun onUpdateCounter(count: Int) {
launch {
findViewById<TextView>(R.id.counter).text = "Image Count : $count"
}
}
....
Activity는 MainContract.View interface를 상속하여 구현을 하고 해당 interface callback 를 Presenter에게 넘겨줍니다.
Presenter로 인해 callback이 호출이 되면 View 를 갱신을 합니다.
추가로 View와 Model은 완벽하게 분리가 되었습니다.
Model
MVC와 달라진 점은 없습니다.
MVP 패턴의 장점
- Presenter에서 Anroid Framework 의 종속성이 제거가 되었습니다. 따라서 테스트 코드 작성이 좀 더 편해졌습니다.
MVC 패턴의 단점
- View와 Presenter 간 1대1로 강력한 종속성이 생겼으며 Presenter는 다른 View와 결합할수 없습니다. 따라서 새로운 View를 생성할 경우 Presenter 또한 생성을 해야합니다.
- 기능이 추가가 될 수록 Presenter 로직 또한 복잡해져서 유지 보수가 어려워 집니다.
마무리
MVP의 Presenter는 MVC 의 Controller와 대비 Android Framework와 종속성이 제거가 되어 테스트 코드 작성 및 Android Framework 변경 사항에 영향을 받지 않는 다는 장점이 있지만 View와 1대1로 종속성 때문에 Presenter는 다른 View와 결합할수 없습니다.
위 장점을 살리면서 단점을 극복한 것이 MVVM 입니다. 다음 포스트에서 기존 MVP에서 MVVM 으로 변경하는 과정을 다루도록 하겠습니다.
댓글남기기