지금까지 우아한테크코스 미션들은 모두 Console 환경이었습니다.
대부분 매 미션별로 MVC 패턴을 반복적으로 적용하면서, 어느 정도 익숙해짐에 따라 비교적 코드를 수월하게 작성할 수 있었을 것입니다.
하지만 이번 주차에 들어서면서 새로운 요구사항이 추가되었습니다.
기존 Domain은 그대로 유지한 채로, Console 환경을 Android로 옮기는 순간 문제가 발생하였습니다.
"앱 UI를 적용할 때 도메인 객체의 변경을 최소화해야 한다."
처음에는 몰랐지만 아래와 같은 상황에서 문제가 발생했습니다.
Console에서 Custom Listener를 사용한 경우
Console은 I/O 기반으로 이루어져 있습니다.
별도로 Event listener interface를 만든다면, 마치 이벤트 기반 프로그래밍처럼 프로그래밍을 할 수 있습니다.
InputView와 OutputView에서 Listener를 구현하면 아래와 같습니다.
// Custom EventListener
interface OmokGameEventListener {
fun onStartTurn(stoneColor: StoneColor, position: Position)
fun onEndTurn(players: Players)
fun onStartGame()
fun onEndGame(winnerStoneColor: StoneColor)
}
// View
interface OutputView : OmokGameEventListener
class OmokOutputView : OmokGameEventListener {
override fun onStartGame() {
println("게임이 시작되었습니다!")
}
override fun onEndGame(winnerStoneColor: StoneColor) {
println("$winnerStoneColor 승리! 게임이 종료되었습니다!")
}
override fun onStartTurn(stoneColor: StoneColor, position: Position) {
println("$stoneColor 차례입니다!")
println("마지막 돌 위치 : $position")
}
override fun onEndTurn(players: Players) {
// 보드를 그린다.
}
// Controller
class Controller(
protected val inputView: InputView,
protected val outputView: OutputView,
)
이러한 구조는 Console에서도 마치 Event-Driven 프로그래밍처럼 만들어줍니다.
Domain 또는 Controller에서 특정 Event가 발생했을 때, 어떠한 ui 로직을 수행할지 View에서 정의해 줄 수 있습니다.
기본적으로 MVC 패턴에서 View를 interface화 함으로써, Controller를 재사용할 수 있습니다.
ex) Android, Console에서 동일한 Controller를 사용할 수 있습니다.
하지만, 만약 위 Controller를 Android에서 재사용해야 한다면 어떨까요?
어떠한 입출력이 이루어지기 전까지 main thread가 block되는 Console 환경과는 다르게 Android는 그렇지 않습니다.
Click, Touch 등 여러 이벤트가 발생했을 때, 어떤 action을 취할 것인지 별도의 Custom Listener(위에서는 OmokGameEventListener가 이에 해당함) 없이도 정의할 수 있습니다.
따라서 위와 같은 코드는 Android에서 적용하기 힘든 구조입니다.
Event가 발생하면 다시 다른 Event가 발생하게끔 설계해야 하기 때문입니다.
안드로이드의 Activity는 View다.
Activity에 대한 여러 관점이 있을 수 있지만, 리뷰어님께 여쭤본 결과, Android에서 Activity는 View라는 답변을 얻었습니다.
그러나 MVC 패턴에서는 Activity를 Controller 관점에서 바라보기 때문에,
아래와 같은 구조로 생각해볼 수 있습니다.
- View - Layout
- Controller - Activity, Fragment
- Model(Data) - DB, Domain
위에서 언급했듯이 Android에서 Activity는 View로 간주합니다.
그러나 MVC 패턴에 맞추려고 하다 보니, Activity가 Controller 역할을 맡게 된 것입니다.
문제는 여기서 발생합니다.
Activity는 View의 역할을 수행해야 하는데, Controller로 간주하다 보니 UI, Domain, Model이 짬뽕되는 스파게티 코드가 됩니다.
아래 코드는 Android MVC 패턴을 사용하면서 발생하는 스파게티 코드 예시입니다.
// MainActivity.kt
private fun continuePreviousGame() {
omokRepo.getAll { // -- Database
if (it.count % 2 == 1) { // -- Business Logic
toggleTurnHolder(StoneColorModel.BLACK) // -- Ui Logic
}
while (it.moveToNext()) {
...
if (putResult is PutSuccess) {
drawStoneOnBoard(omokIv, putResult.stoneColor)
}
}
}
}
MVC 패턴에서는 Activity를 Controller로 간주하기 때문에 MVC 패턴에 위배되는 구조는 아닙니다.
하지만 스파게티 코드는 고결합성(High-coupling), 저응집도(Low-cohesion)를 가지기 때문에, 프로그램의 유지보수를 어렵게 만드는 문제가 있습니다.
그래서 MVC 패턴은 규모가 큰 Android 프로젝트에서 더 이상 잘 사용되지 않으며, 어떠한 관점에서는 Anti 패턴으로 간주하기도 합니다.
이러한 이유로, MVC를 MVC답게(?) 사용하기 위해 MVP 패턴이 등장하였습니다.
MVP 패턴에서는 Activity를 완전한 View로 간주하고, Presenter에서 기존 MVC의 Controller 역할을 수행합니다.
따라서 Activity는 관심사가 오로지 View 하나이며, Presenter는 View와 Model의 중간 다리 역할에 집중할 수 있습니다.
'대외활동 > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스] 레벨3 프로젝트[커디]를 마치며.. (4) | 2023.08.21 |
---|---|
[우아한테크코스] 📚 레벨로그 - 레벨1 인터뷰 (0) | 2023.03.30 |
[우아한테크코스] 함수형 프로그래밍(Functional Programming)이란? (4) | 2023.03.08 |
[우아한테크코스] 우아한테크코스 한 달 생활기 (2) | 2023.03.05 |
[우아한테크코스] 원시값 포장과 일급컬렉션은 무엇이고, 어디까지 감싸야 할까? (1) | 2023.02.27 |