[Design Pattern] 4. MVC, MVP, MVVM 패턴

김민경·2025년 6월 23일

Design Pattern

목록 보기
7/9
post-thumbnail

배경

MVC, MVP, MVVM 패턴은 모두 UI 코드와 비즈니스 로직을 분리하기 위한 아키텍처 패턴

초기 프로그램은 로직과 UI가 한 파일에 섞여 있었기 때문에 UI 복잡성 증가, 코드 관리에 어려움
-> UI를 조금만 바꿔도 비즈니스 로직까지 수정해야 하는 유지보수 문제 발생, 테스트에 어려움

따라서 UI와 비즈니스 로직을 분리가 필요!
UI(View), 데이터 처리(Model), 로직 처리(Controller, Presenter, ViewModel) 을 분리하여 각 부분을 독립적으로 수정하고, 테스트 진행

결국 더 깔끔하고, 더 테스트하기 쉬우며, 더 유지보수하기 좋은 소프트웨어를 만들기 위해 등장

MVC 패턴

Model-View-Controller의 약자로 UI, 데이터, 제어 로직을 각각 분리하여, 유지보수성과 확장성을 높이는 소프트웨어 디자인 패턴

  • Model : 데이터, 비즈니스 로직 담당
  • View : 사용자에게 보여지는 UI 담당
  • Controller : 사용자 입력 처리, Model과 View를 연결

동작 흐름

  1. Controller로 사용자의 Action이 들어옴
  2. Controller는 사용자의 Action을 확인하고, Model 업데이트
  3. ControllerModel을 나타내 줄 View를 선택
  4. ViewModel을 이용하여 화면을 나타냄

Controller는 여러 개의 View를 선택할 수 있는 1:n 구조
ControllerView를 선택할 뿐 직접 업데이트 하지는 않음 (ViewController를 알지 못함)

예제

//Controller
@Controller
public class BookController {

    @GetMapping("/book")
    public String getBook(Model model){
        Book book = new Book("Clean Code", "Robert Martin");
        model.addAttribute("book", book);
        return "bookView";
    }
}

//Model
public class Book {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }
}
//View
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Book Info</title>
</head>
<body>
    <h1>📚 Book Information</h1>
    <p>Title: <span th:text="${book.title}"></span></p>
    <p>Author: <span th:text="${book.author}"></span></p>
</body>
</html>

특징

  1. 관심사를 분리하여 코드의 역할이 명확

  2. 유지보수와 테스트에 용이
    UI, 데이터, 제어 로직을 독립적으로 수정 가능하며 비즈니스 로직(Model) 테스트가 쉬움

  3. 유연한 UI 교체 가능
    Model은 그대로, View만 변경 가능한 완벽하게 분리된 구조

  4. 하지만 ModelView의 의존성이 굉장히 크다는 단점

결론

역할별로 코드를 분리하여 유지보수를 쉽게 만들고, UI와 비즈니스 로직을 유연하게 관리할 수 있게 해주는 패턴

하지만 ModelView의 의존성이 크기 때문에, Controller가 불필요하게 커질 수 있음 -> 이를 보안하기 위해 MVP 패턴 등장


MVP 패턴

Model-View-Presenter의 약자로 Model과 View는 MVC 패턴과 동일하지만 Controller 대신 Presenter가 존재

  • Model : 데이터, 비즈니스 로직 담당
  • View : UI 표시, 사용자 입력 이벤트만 전달 (데이터 처리 X)
  • Presenter : View와 Model 사이의 중재자, UI 로직 및 데이터 처리 담당

동작 흐름

  1. 사용자가 View에서 이벤트 발생
  2. View가 이벤트를 Presenter에게 전달
  3. PresenterModel을 호출하여 데이터 처리
  4. PresenterView를 갱신 (View에 직접 명령)

PresenterViewModel의 인스턴스를 가지고 있어 둘을 연결하는 접착제 역할
ViewPresenter는 1:1 관계로 완전히 의존하며, View는 단순 UI 렌더링만 담당

예제

계산을 담당하는 Model과 결과를 반환하는 View

public class CalculatorModel {
    public int add(int a, int b){
        return a+b;
    }
}

public interface CalculatorView {
    void showResult(int result);
}

public class CalculatorConsoleView implements CalculatorView{
    @Override
    public void showResult(int result) {
        System.out.println("Result : " + result);
    }
}

ViewModel을 이어주는 Presenter

public class CalculatorPresenter {
    private final CalculatorModel model;
    private final CalculatorView view;

    public CalculatorPresenter(CalculatorModel model, CalculatorView view) {
        this.model = model;
        this.view = view;
    }

    public void onAddButtonClicked(int a, int b){
        int result = model.add(a,b);
        view.showResult(result);
    }
}

public class CalculatorMain {
    public static void main(String[] args) {
        CalculatorModel model = new CalculatorModel();
        CalculatorView view = new CalculatorConsoleView();
        CalculatorPresenter presenter = new CalculatorPresenter(model, view);

        presenter.onAddButtonClicked(10,20);
    }
}
/*
Result : 30
*/

특징

  1. View, Model의 완벽 분리
    ViewPresenter의 명령만 수행 -> UI 코드 단순화
    PresenterView를 인터페이스로 참조 -> 플랫폼 독립적 설계 가능
  2. 하지만 구현 코드 증가
    View Interface, Presenter 등 코드가 많아지며, 복잡한 화면 구조에는 Presenter가 비대해질 수 있음

결론

ViewModel을 철저히 분리하여 테스트 가능한 구조를 만드는 패턴

MVC보다 View의 역할이 훨씬 더 축소되고, ViewModel의 의존성은 해결되었지만, ViewPresenter의 의존성이 강해지는 문제 발생
-> 이를 보안하기 위해 MVVM 패턴의 등장


MVVM 패턴

Model-View-ViewModel의 약자로, View와 Model을 데이터 바인딩으로 완전히 분리하는 구조로, 주로 양방향 데이터 바인딩이 지원되는 프레임워크에서 사용되는 디자인 패턴

  • Model : 데이터, 비즈니스 로직 담당
  • View : UI 화면(버튼, 텍스트박스 등)
  • ViewModel : View를 표현하기 위해 만들어진 View를 위한 Model
    View와 Model 사이를 중재, UI 상태를 관리하는 객체

동작 흐름

  1. 사용자가 View에 요청
  2. 요청이 들어오면 Command 패턴으로 ViewModel에 명령을 함
  3. ViewModel은 필요한 데이터를 Model에 요청 및 응답
  4. ViewModel은 응답 받은 데이터를 가공하여 저장
  5. ViewViewModel과의 Data Binding으로 인해 자동 갱신

ViewModel 사이의 의존성이 없으며, Command 패턴과 Data Binding을 사용하여 ViewViewModel의 의존성도 제거 (ViewViewModel의 값을 감시만 하고, 직접 제어하지는 않는 직접 동기화된 구조)

Data Binding : 데이터와 화면을 실시간으로 동기화하는 기술

예제

ViewModelModel을 직접 사용하여 데이터를 주입하거나 가져옴

// 💡 Model: 데이터 객체 (비즈니스 로직을 담을 수도 있음)
data class User(val name: String, val age: Int)

ViewModel은 UI를 위한 상태 관리자
LiveData를 사용해 데이터가 바뀌면 View에 자동으로 반영되도록 구성

  • loadUser() 호출 시 -> ViewModel의 데이터 갱신 -> View 자동 업데이트
class UserViewModel : ViewModel() {

    // 💡 View에 실시간으로 반영될 데이터를 LiveData로 선언
    val userName = MutableLiveData<String>()
    val userAge = MutableLiveData<Int>()

    // 💡 Model의 데이터를 ViewModel에 로드하는 메서드
    fun loadUser() {
        val user = User("채영은", 25)
        userName.value = user.name  // View에 실시간으로 반영됨
        userAge.value = user.age    // View에 실시간으로 반영됨
    }
}

ViewViewModel을 바인딩하여 데이터 변경을 감시

  • viewModel.loadUser()가 호출되면 → 데이터가 바뀌고 → View가 즉시 갱신됨.
class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 💡 ViewModel 생성 및 바인딩
        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)

        // 💡 ViewModel을 View에 연결 → 이걸 통해 데이터 자동 동기화
        binding.viewModel = viewModel
        binding.lifecycleOwner = this // LiveData를 위한 필수 설정

        // 💡 사용자 화면 진입 시 데이터 로드 요청
        viewModel.loadUser()
    }
}

XML에서 ViewModel의 데이터를 실시간으로 구독하고 있음.

  • ViewModel의 userName이 변경되면 → TextView의 텍스트가 즉시 자동 갱신됨.
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.userName}" />  <!-- 💡 LiveData 바인딩 -->

특징

  1. ViewModel이 철저히 분리되며 ViewModel이 UI 상태를 관리하여 View가 훨씬 간결해짐

  2. 양방향 데이터 바인딩을 통해 자동 UI 업데이트 가능

  3. 모든 모듈이 독립적인 상태로 테스트 용이

  4. 하지만 ViewModel의 설계가 쉽지 않으며 비대해질 위험이 있음

결론

결국 ViewModel을 UI처럼 사용하고, 데이터 바인딩을 통해 ViewViewModel을 실시간으로 동기화하는 패턴.

View는 단순히 ViewModel을 관찰만 하는 역할


최종

MVC

Controller가 다양한 View를 제어
하지만 View에 반환되는 Model이 정해져있기 때문에 상당히 의존적임

따라서 View와 Model의 의존성을 낮추기 위해 ~

MVP

Presenter가 View와 Model 사이에서 View를 직접 제어.
하지만 View와 Presenter 사이의 의존성이 커지는 문제 발생

따라서 View와 Presenter의 의존성을 낮추기 위해 ~

MVVM

ViewModel을 통해 View와 데이터 실시간 동기화
View, Model, ViewModel 모두 의존성이 낮고, 독립적인 모듈로 구성 가능

하지만 그만큼 점점 코드를 구현하는 복잡도가 높아진다는 단점 존재

결론

MVCMVPMVVM
서버 UI수동 바인딩 UI자동 바인딩 UI

사실상 어느 패턴을 사용하는 것에 정답은 없으며, 현재 프로젝트의 크기와 상황에 알맞게 비교하고 사용하는 것이 좋다!


https://magi82.github.io/android-mvc-mvp-mvvm/
https://beomy.tistory.com/43
https://goharry.tistory.com/46

profile
뭐든 기록할 수 있도록

0개의 댓글