질문 정리

MVC, MVP, MVVM 패턴에 대한 정리

five2week 2023. 7. 31. 21:27

MVC 패턴

MVC (Model-View-Controller) 패턴은 사용자 인터페이스를 가진 애플리케이션을 설계하는 데 일반적으로 사용되는 설계 패턴입니다. 이 패턴은 애플리케이션의 개발을 세 가지 구성 요소로 분리함으로써 관심사를 분리합니다.

  1. Model: 이 부분은 애플리케이션의 데이터와 비즈니스 로직을 처리하는 역할을 합니다. 데이터를 저장하고 검색하는 메서드, 비즈니스 로직, 비즈니스 규칙, 그리고 데이터를 조작하는 데 사용되는 계산 등을 포함합니다.
  2. View: 이 부분은 사용자에게 보여지는 UI 부분입니다. 단순히 xml 코드만을 의미하는 것이 아니라, 클릭 리스너나 데이터바인딩을 포함하고 있습니다. 사용자에게 정보를 표시하고 사용자의 명령을 받아들입니다. View는 Model이 가지고 있는 정보를 사용하여 사용자에게 정보를 제공합니다.
  3. Controller: 이 부분은 Model과 View 사이의 인터페이스 역할을 합니다. 사용자의 입력을 받아 Model의 상태를 변경하고, Model의 변경 사항을 View에 반영하도록 지시합니다. 사용자의 요청을 해석하고, 이를 Model에 전달하여 데이터를 변경하거나, Model의 변경을 View에 반영하여 사용자에게 피드백을 제공합니다.

MVC의 장점:

  • 개발 과정이 분리되어 병렬 개발이 가능합니다.
  • 코드 재사용성과 유지 보수성이 좋습니다.

MVC의 단점:

  • View와 Model 사이에 종속성이 없어야 한다는 이론적 전제가 실제 애플리케이션에서는 항상 지켜지지 않습니다.
  • Controller가 복잡해질 수 있습니다.

안드로이드 프로젝트에서 MVC가 잘 사용되지 않는 이유:

실제로 Android 애플리케이션 개발에서는 액티비티가 뷰와 컨트롤러의 역할을 동시에 수행하는 경우가 많습니다. 액티비티는 레이아웃 XML과 연결되어 화면에 UI를 보여주는 역할을 하면서, 동시에 사용자의 입력을 받아 처리하는 역할도 합니다.

이는 MVC 패턴의 원래 의도와는 약간 다른데, MVC 패턴은 뷰와 컨트롤러를 완전히 분리하여 각각의 역할을 명확하게 하는 것을 목표로 합니다. 그러나 Android에서 이를 완전히 분리하기는 어렵습니다. 이런 이유로 Android 개발에서는 MVP나 MVVM과 같은 다른 아키텍처 패턴이 더 많이 사용됩니다. 이런 패턴들은 액티비티를 뷰로 두고, 이를 제어하는 로직을 별도의 Presenter나 ViewModel 클래스로 분리하여 뷰와 비즈니스 로직을 분리하는 것을 목표로 합니다.

 

class UserController(private val repository: UserRepository) {
    fun getUser(id: Int): User {
        return repository.getUser(id)
    }
}

class UserActivity : AppCompatActivity() {
    private lateinit var controller: UserController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        controller = UserController(UserRepository())

        // View 역할: 사용자의 입력에 반응하고 UI를 업데이트하는 등의 작업 수행
        val button: Button = findViewById(R.id.user_button)
        button.setOnClickListener {
            val user = controller.getUser(1)
            displayUser(user)
        }
    }

    fun displayUser(user: User) {
        val textView: TextView = findViewById(R.id.user_text)
        textView.text = "Displaying user: ${user.name}"
    }
}

data class User(val id: Int, val name: String, val email: String)

class UserRepository {
    fun getUser(id: Int): User {
        return User(id, "Test User", "testuser@example.com")
    }
}

 

MVP 패턴

MVP (Model-View-Presenter) 패턴은 MVC (Model-View-Controller) 패턴을 기반으로 하지만, Controller 대신 Presenter를 사용하여 사용자 인터페이스와 데이터 간의 상호 작용을 관리합니다. 이 패턴은 View와 Model 사이의 독립성을 더욱 강조합니다.

  1. Model: Model은 애플리케이션의 비즈니스 로직을 담당하는 부분으로, 데이터를 처리하고 저장합니다. 비즈니스 로직, 비즈니스 규칙, 데이터에 대한 계산, 그리고 데이터를 저장하고 검색하는 메서드 등을 포함합니다.
  2. View: View는 사용자에게 표시되는 UI 부분입니다. 사용자에게 정보를 제공하고, 사용자의 명령을 받습니다.
  3. Presenter: Presenter는 View와 Model 사이의 연결고리로 작동하며, 모든 비즈니스 로직을 처리합니다. View에서 발생하는 사용자의 동작을 받아 Model을 업데이트하고, Model에서의 변경 사항을 다시 View에 반영합니다. View와 Model이 직접적으로 통신하는 것이 아니라 Presenter를 통해 통신하는 것이 이 패턴의 핵심입니다.

MVP 패턴의 주요 장점은 View와 Model 사이의 분리를 통해 테스트와 유지 보수가 용이하다는 점입니다. View는 Presenter에 의해 완전히 제어되기 때문에 View와 관련된 모든 동작은 Presenter를 통해 테스트할 수 있습니다.

단점으로는 Presenter가 과도하게 복잡해질 수 있다는 점이 있습니다. Presenter는 View와 Model 사이의 모든 통신을 관리하므로, 복잡한 애플리케이션에서는 Presenter가 너무 많은 역할을 담당하게 될 수 있습니다. 이로 인해 Presenter의 코드가 복잡해질 수 있으며, 이를 관리하는 것이 어려울 수 있습니다.

 

MVP의 장점:

  • View와 Model 사이의 완전한 분리로 테스트와 유지 보수가 쉬워집니다.
  • 데이터 바인딩을 수행하지 않아도 View를 업데이트 할 수 있습니다.

MVP의 단점:

  • Presenter가 과도하게 복잡해질 수 있습니다.
  • 크고 복잡한 시스템에서는 이해하고 유지하기가 어려울 수 있습니다.
// View interface
interface UserView {
    fun displayUser(user: User)
}

// Presenter
class UserPresenter(private val view: UserView, private val repository: UserRepository) {
    fun handleClick() {
        val user = repository.getUser(1)
        view.displayUser(user)
    }
}

class UserActivity : AppCompatActivity(), UserView {
    private lateinit var presenter: UserPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        presenter = UserPresenter(this, UserRepository())

        val button: Button = findViewById(R.id.user_button)
        button.setOnClickListener {
            presenter.handleClick()
        }
    }

    override fun displayUser(user: User) {
        val textView: TextView = findViewById(R.id.user_text)
        textView.text = "Displaying user: ${user.name}"
    }
}

data class User(val id: Int, val name: String, val email: String)

class UserRepository {
    fun getUser(id: Int): User {
        return User(id, "Test User", "testuser@example.com")
    }
}

MVVM 패턴

MVVM (Model-View-ViewModel)은 사용자 인터페이스 기반의 애플리케이션 설계에 널리 사용되는 디자인 패턴입니다. 이 패턴은 특히 Microsoft의 WPF(Windows Presentation Foundation)와 같이 데이터 바인딩에 의존하는 환경에서 잘 작동합니다.

  1. Model: Model은 데이터와 비즈니스 로직을 처리합니다. 데이터를 저장하고 검색하는 메서드, 비즈니스 로직, 비즈니스 규칙, 그리고 데이터를 조작하는 데 사용되는 계산 등을 포함합니다.
  2. View: View는 사용자에게 표시되는 UI 부분입니다. 사용자에게 정보를 제공하고 사용자의 명령을 받습니다.
  3. ViewModel: ViewModel은 View를 위한 Model로, View를 위한 데이터를 제공하고 이를 처리하는 로직을 담당합니다. ViewModel은 Model의 데이터를 가공하여 View가 이해하고 표시할 수 있는 형태로 만듭니다. 또한, 사용자의 액션을 Model에 반영하고, Model의 업데이트를 View에 반영합니다.

MVVM의 주요 장점은 View와 ViewModel 사이의 데이터 바인딩을 통해 코드 비하인드를 최소화하고, 따라서 테스트 가능성과 유지 보수성을 높일 수 있다는 점입니다. ViewModel은 View에 대해 알지 못하므로, View를 변경하거나 교체해도 ViewModel의 코드를 변경할 필요가 없습니다.

단점으로는 데이터 바인딩에 대한 이해가 필요하다는 점이며, 이로 인해 복잡성이 증가할 수 있습니다. 디버깅도 어려워질 수 있습니다. 또한, MVVM 패턴을 적용하는 초기 구현 단계에서는 MVC나 MVP보다 더 많은 작업이 필요할 수 있습니다.

 

MVVM의 장점:

  • 높은 수준의 모듈화로 인해 개발과 유지 보수가 쉽습니다.
  • View와 ViewModel 사이의 데이터 바인딩을 통해 쉽게 View를 업데이트 할 수 있습니다.
  • View와 Model 사이의 종속성이 없습니다.

MVVM의 단점:

  • 데이터 바인딩으로 인해 디버깅이 어려울 수 있습니다.
  • 설계가 복잡하며, 학습 곡선이 가파릅니다.
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    val userLiveData = MutableLiveData<User>()

    fun getUser(id: Int) {
        val user = repository.getUser(id)
        userLiveData.value = user
    }
}

class UserActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)

        viewModel.userLiveData.observe(this, Observer { user ->
            displayUser(user)
        })

        val button: Button = findViewById(R.id.user_button)
        button.setOnClickListener {
            viewModel.getUser(1)
        }
    }

    fun displayUser(user: User) {
        val textView: TextView = findViewById(R.id.user_text)
        textView.text = "Displaying user: ${user.name}"
    }
}

data class User(val id: Int, val name: String, val email: String)

class UserRepository {
    fun getUser(id: Int): User {
        return User(id, "Test User", "testuser@example.com")
    }
}

MVC, MVP, MVVM의 주요 차이점

  • MVC와 MVP는 구조적 차이가 있습니다. MVC에서 Controller는 사용자의 입력에 반응하는 반면, MVP에서 Presenter는 View에서 발생하는 이벤트에 반응합니다.
  • MVC와 MVP는 View가 Model을 직접 업데이트하는 반면, MVVM에서는 ViewModel이 이 역할을 합니다.
  • MVP와 MVVM의 차이점 중 하나는 MVVM이 데이터 바인딩을 지원한다는 점입니다. 이로 인해 MVVM은 View의 코드 비하인드를 최소화하고, 따라서 테스트 가능성과 유지 보수성을 높일 수 있습니다.

 

적용할 패턴을 선정하는 방법

절대적으로 좋은 패턴은 없습니다. 어플리케이션이 작고 화면 수가 적은 경우 MVC나 MVP가 효율적일 수 있습니다. MVC는 컨트롤러가 뷰와 모델을 모두 관리하기 때문에 구현이 비교적 간단합니다. MVP는 뷰와 모델을 완전히 분리하여 테스트와 유지보수가 쉽습니다.

반면에 애플리케이션이 복잡하고 화면 수가 많은 경우 MVVM이 더 효과적일 수 있습니다. MVVM은 뷰모델이 데이터 바인딩을 통해 뷰와 모델 사이에 자동으로 상호작용을 관리하므로, 복잡한 상호작용을 더 쉽게 처리할 수 있습니다. 또한 MVVM은 뷰와 뷰모델이 각각 독립적으로 테스트할 수 있어 테스트 용이성이 좋습니다. 따라서 프로젝트에 규모와 기능을 고려하여 적절한 패턴을 선택하는 것이 중요합니다.