-
[CS] MVP 패턴 바로알기개발/CS 2025. 4. 6. 20:54
들어가며
지난 시간에는 MVC 패턴을 알아보았습니다. MVC의 탄생 배경, 특징, 장단점과 안드로이드 개발에서 문제점을 살펴보았습니다.
MVC 패턴에 관한 자세한 이야기는 아래 포스팅을 참고해주세요!
[CS] MVC 패턴 바로알기
들어가며지난 시간에는 소프트웨어 아키텍처 패턴이 무엇인지 알아보았습니다. 소프트웨어 디자인 패턴을 함께 알아보며, 소프트웨어 설계에서 사용하는 두 패턴의 개념을 잡을 수 있었습니다
walnut-dev.tistory.com
이번 시간에는 MVC 패턴의 문제점을 해결하고자 고안된 MVP 패턴을 알아보겠습니다.
MVC 패턴의 문제점
지난 포스팅에서 알아보았듯, MVC 패턴은 컴퓨터 시스템을 Model-View-Controller로 각 역할을 분리하여, 유지 보수를 유리하게 하고 사용자와 시스템의 모델 간 이해 관계를 맞춰줍니다. 이로써 사용자 친화적인 시스템 설계에 도움을 주었습니다.
그러나 아래와 같은 문제점들이 존재했습니다.
- Massive-ViewController : 시스템 규모가 거대해지고 복잡해지면서, 너무 많은 View를 관리하는 등 Controller의 역할이 비대해질 수 있습니다. 너무 뚱뚱한 Controller로 유지 보수성이 저하될 수 있습니다.
- View와 Model 간 의존성 발생 가능 : Controller의 역할이 과잉되면서, View와 Model이 Controller 안에서 복잡하게 얽힐 수 있습니다. 특히 안드로이드 개발에서는 Activity가 View와 Controller 역할을 동시에 처리하며, Model과 View 사이 의존 관계가 발생할 수 있습니다.
이런 문제를 해결하기 위해 MVP 패턴이 탄생했습니다.
MVP 패턴
정의
Model–view–presenter - Wikipedia
From Wikipedia, the free encyclopedia Software design pattern Diagram that depicts the model–view–presenter (MVP) GUI design pattern Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern, and is use
en.wikipedia.org
MVP는 Model, View, Presenter로 구성된 패턴으로, MVC 패턴에서 파생되었습니다.
MVP는 사용자 인터페이스를 구축할 때 사용하는 아키텍처 패턴이며, 자동화된 단위 테스트를 용이하게 하고 프레젠테이션 로직의 관심사 분리를 개선하도록 설계되었습니다.
구조
MVP 패턴의 구조는 MVC와 거의 유사합니다. MVC로부터 파생되기도 했고, 관심사에 따라 분리된 구성요소의 각 역할이 비슷하기 때문입니다.
Model
MVC 패턴의 Model과 동일합니다.
- 프로그램 상에서 도메인과 비즈니스 로직을 구현하는 객체이다.
- UI에 나타낼 정보를 담고 있다.
- Presenter로부터 데이터와 이벤트를 전달받아 변경된다.
View
MVC 패턴의 View와 거의 동일합니다. 하지만 사용자와 직접 상호작용한다는 점에서 차이가 있습니다.
- UI 로직을 구현한다.
- Presenter로부터 데이터(Model의 값)을 전달받아 사용자에게 보여준다.
- 사용자와 상호작용을 담당하고, 이벤트를 Presenter에게 전달한다.
Presenter
MVC 패턴의 Controller 역할과 유사합니다. 그러나 사용자와 직접 상호작용하지 않습니다.
- Model과 View 사이 중간 관리자 역할을 한다.
- View로부터 이벤트를 전달받아 처리하며, Model을 가공한다.
- Model의 데이터를 View에게 전달한다.
- 일반적으로 View와 1:1 관계이다.
좀 더 상세한 MVP 패턴의 동작 방식은 위 그림을 참고하시면 좋습니다.
출처: 안드로이드에는 MVC 아키텍처 패턴이 없다: 제임스 블로그
안드로이드에는 MVC 아키텍처 패턴이 없다
안드로이드에서 대표적인 아키텍처 패턴으로 MVC, MVP, MVVM이 거론된다. 사실상 MVC 패턴은 거의 사용되지 않는다. 더 좋은 아키텍처 패턴이 등장해서 사장된 것일 수도 있지만 여전히 다른 분야에
brunch.co.kr
MVP의 탄생 배경
MVP 패턴은 1990년대 초에 설립된 Apple, IBM, Hewlett-Packard의 합작 회사, Taligent에서 시작되었습니다.
Taligent는 객체 지향 운영체제 TalOS를 개발하던 회사였으며, 이 OS는 이후 CommonPoint라는 이름으로 변경되었습니다. 더 자세한 이야기는 위키 백과와 Taligent에 관한 문서를 참고해주세요.
MVP 패턴은 Taligent가 만든 C++ 기반의 CommonPoint 환경에서 기본 프로그래밍 모델로 사용되었습니다. Taligent는 나중에 MVP 패턴을 C++에서 Java로 마이그레이션했고, Taligent의 CTO인 Mike Potel이 MVP 패턴에 대한 논문을 내며 널리 알려졌습니다.
논문의 내용을 간단하게 정리하자면, 데이터(Model)을 **선택(Selections)**하고 **가공(Commands)**하며, View로부터 전달된 이벤트가 데이터 변화에 매핑되도록 **상호작용(Interactors)**하는 역할로 Presenter를 만들었다고 합니다. Controller와 유사하지만 MVC의 Controller의 개념과 구분하기 위해서 Presenter라고 이름지어졌습니다.
MVP: Model-View-Presenter 논문의 MVP 도식 (저자: Mike Potel, CTO in Taligent, Inc.) MVP에 대한 설명 중
This represents the function of the classic Smalltalk controller, but elevated to an application level and taking into account the intermediate selection, command, and interactor concepts. To capture this distinction we refer to this kind of controller as a presenter. (중략) The role of the presenter within MVP is to interpret the events and gestures initiated by the user and provide the business logic that maps them onto the appropriate commands for manipulating the model in the intended fashion. (중략) The presenter then represents the traditional "main" or "event loop" part of the application, creating the appropriate models, selections, commands, views, and interactors, and providing the business logic that directs what happens when, like a traffic cop or orchestra conductor.
논문에서도 MVC의 Controller와 비슷한 역할을 한다고 설명하며, 오케스트라 지휘자 또는 교통 경찰에 비유하는 것을 알 수 있네요.해당 논문에서는 컴퓨터가 사용자와 상호작용하는 여러 방법이 언급되어 있습니다. 마우스 움직임과 클릭, 키보드 입력, 디스크 삽입에 이어, 심지어는 드래그&드롭, 체크박스, 펜과 같은 그리기 도구, 음성 녹음까지, 오늘날의 컴퓨터가 갖춘 입력 기능에 대해서도 이야기합니다.
기술이 발전하면서 컴퓨터가 더욱 다양한 방법으로 상호작용을 할 수 있게 되었고, 그로 인해 UI가 더욱 복잡, 다양해짐에 따라 MVC의 한계를 해결할 수 있는 설계 패턴이 필요해졌을 것입니다. 이러한 필요성에 따라 MVP와 같은 패턴이 파생된 것이 아닐지 생각이 듭니다.
MVP 패턴 활용 예시(Android)
Android 개발에서 MVP 패턴을 어떻게 적용할 수 있는지 간단한 예제로 알아보겠습니다.
+, - 버튼으로 카운트를 증가, 감소시키는 View를 구현한다고 가정해봅니다.
Model
카운트를 갖는 도메인 모델 Count를 생성합니다. 외부에서 함부로 값을 변경할 수 없고, 증가, 감소 요청을 받아 Count가 스스로 값을 변경합니다.
class Count(number: Int = 0) { var number: Int = number private set fun increase() { number++ } fun decrease() { number-- } }
Contract
구글에서 안드로이드에 MVP 패턴을 적용한 대표적 예제에서는 Contract라는 인터페이스를 활용한 방법을 소개했습니다. (현재는 구글이 해당 예제 레포지토리를 제공하고 있지 않지만, 구글링하면 다른 글에서 쉽게 찾아볼 수 있습니다.)
Contract는 “계약”, “계약서” 이라는 뜻에 걸맞게, 구성 요소가 행할 동작을 명시한 명세서 역할을 합니다. 각 구성 요소가 이러한 동작을 행할 것임을 Contract에서 추상적으로 명시해, 다른 구성 요소가 Contract에 따라 요청할 수 있습니다.
interface CountContract { interface View { fun showCount(count: Int) } interface Presenter { fun increaseCount() fun decreaseCount() } }
Contract에서는 View와 Presenter를 명세합니다. View와 Presenter는 서로에게 요청하고 각자의 역할을 수행하면서도, 서로가 추상화된 interface를 참조하기 때문에 결합도를 낮추고 변경 사항에 유연하게 대처할 수 있습니다.
이 때 중요한 점은, View와 Presenter는 어떠한 값도 반환하지 않습니다. View와 Presenter가 Contract에 명시된 요청을 통해 긴밀히 협력하도록 구성하는 것이 목적입니다.
Model이 할 동작은 이곳에서 명시하지 않습니다. 이는 Model은 여러 개가 존재할 수 있어 관리 포인트가 불필요하게 증가할 수 있으며, 한 Contract 안에 View와 Model의 역할을 함께 명시함으로 인해 View와 Model 간 참조가 발생할 가능성이 존재하기 때문입니다. 또한 MVP 패턴은 특정 도메인에 종속되지 않는 설계 패턴이기 때문에, Model에 대한 역할은 Contract에 담지 않는 것이 바람직하다고 생각합니다.
Presenter
Presenter는 Contract.Presenter를 구현하며, Contract에 명시된 View interface를 참조합니다.
그리하여 Model과 함께 적절히 처리한 후, 데이터를 View에게 넘겨주어 화면의 변경을 요청합니다.
// Contract로 추상화된 View를 참조 class CountPresenter(private val view: Contract.View) : Contract.Presenter { private val count = Count() // View가 카운트 증가를 요청 override fun increaseCount() { count.increase() // Model에게 카운트 증가 요청 view.show(count.number) // Presenter는 View에게 데이터 업데이트를 요청 } // View가 카운트 감소를 요청 override fun decreaseCount() { count.decrease() // Model에게 카운트 감소 요청 view.show(count.number) // Presenter는 View에게 데이터 업데이트를 요청 } }
View
View도 마찬가지로 Contract.View를 구현하며, Contract에 명시된 Presenter interface를 참조합니다.
직접 CounterPresenter 구현체를 생성하여 인스턴스화 하지만, Contract.Presenter를 통해 협력하기에 낮은 의존성을 갖습니다.
사용자와 상호작용으로 전달받은 이벤트를 Presenter에 전달하고(Presenter에게 적절한 처리를 요청), Presenter로부터 전달받은 데이터로 화면을 변경합니다.
class CountActivity : AppCompatActivity(), Contract.View { private val presenter: Contract.Presenter by lazy { CountPresenter(this) } override fun onCreate() { // ViewBinding을 사용한다고 가정했습니다. binding.btnIncreaseCount.setOnClickListener { // 카운트 증가 버튼 클릭 시 presenter에게 카운트 증가 요청 presenter.increaseCount() } binding.btnDecreaseCount.setOnClickListener { // 카운트 감소 버튼 클릭 시 presenter에게 카운트 감소 요청 presenter.decreaseCount() } } // Contract에 명시된 showCount 메서드, Presenter가 데이터 업데이트를 요청 override fun showCount(count: Int) { // Presenter에게 전달받은 데이터로 화면 업데이트 binding.tvCount.text = "$count" } }
MVP 패턴의 장단점
장점
MVP의 특징과 탄생 일화, 구현 방법을 간단히 살펴보았으니, MVC에 비해서 어떤 장점이 있는지 알아보겠습니다.
UI 로직과 비즈니스 로직을 명확하게 분리한다
MVC의 Controller는 Model 관리 및 가공과 View를 나타내는 방식(배치, 가시성 등) 또한 담당했습니다. 즉, Controller는 비즈니스 로직을 구현하면서도 UI 로직을 구현합니다.
MVP는 View가 UI 로직을, Presenter가 비즈니스 로직을 담당합니다. 이로써 관심사에 따른 역할 분리가 더욱 명확해지고 유지보수도 편리해졌으며, 비즈니스 로직의 테스트도 수월해졌습니다.
View와 Model간 결합도를 낮춘다
UI 로직이 View의 책임이 되면서, Presenter는 Model의 데이터를 View에게 전달해주는 역할을 합니다. 이로써 Massive ViewController의 문제점을 완화시켰습니다.
단점
MVC 패턴의 문제점을 개선했음에도, 여전히 해결하지 못한 문제점이 존재합니다.
View와 Presenter 간 결합도가 높다
View와 Presenter는 서로를 알고 있습니다. 물론 인터페이스(Contract)로 추상화하여 약한 참조를 이룰 수 있지만, 그럼에도 의존성이 존재합니다.
서로 간의 의존성 때문에 Presenter의 테스트 작성에 제약이 생기고, 다른 형태의 View에서는 Presenter가 재사용되기 어렵다는 단점이 있습니다.
프로젝트 규모가 커질수록 관리가 어렵다
View와 Presenter 사이 높은 결합로 인해, 시스템 규모가 커져 View가 많아지거나 복잡해지면, 그에 따라 Presenter의 개수와 복잡도도 증가합니다. 결국 관리 포인트가 늘어나 유지 보수가 어려워질 수 있습니다.
마치며
오늘은 MVP 패턴을 가볍게 파헤쳐보았습니다.
MVP 패턴은 결국 시스템이 거대해지고 복잡해지면서 나타난 MVC가 가진 문제점들을 해결하고자 탄생했습니다.
UI 로직과 비즈니스 로직을 분리해 View와 Model 간 결합도를 낮추며 MVC의 문제점을 해결했으나, View와 Presenter 간 의존성이 남아있다는 문제가 남아있습니다.
View와 Presenter 간 의존성을 분리하기 위해 여러 패턴이 파생되었고, 그 중 하나가 MVVM 패턴입니다.
다음 포스팅은 MVVM 패턴을 함께 공부하며, MVP의 문제점을 어떻게 해결했는지 알아보겠습니다.
참고 자료
'개발 > CS' 카테고리의 다른 글
[CS] MVC 패턴 바로알기 (0) 2025.04.06 [CS] 아키텍처 패턴 딥다이브하기(feat. 디자인 패턴) (0) 2025.03.31 [CS] SOLID 쉽고 가볍게 맛보기 (0) 2025.03.01