소프트웨어 디자인 패턴 - 3. MVVM 패턴 (Model-View-ViewModel)

2025. 1. 20. 12:07소프트웨어/디자인 패턴

MVVM 패턴이란?

MVVM 패턴은 애플리케이션을 Model, View, ViewModel의 세 가지 역할로 나누어 설계하는 소프트웨어 디자인 패턴입니다. 이 패턴은 특히 데이터 바인딩을 지원하는 프레임워크에서 효과적입니다. ViewModel이 View와 Model 사이에서 중간 역할을 하며, View와 데이터를 동기화하는 데 초점이 맞춰져 있습니다.


구성 요소

1. Model

  • 데이터를 관리하고 저장.
  • 비즈니스 로직과 데이터베이스와의 상호작용을 담당.

2. View

  • 사용자 인터페이스(UI)를 나타냄.
  • 사용자와 상호작용하며 데이터를 표시.
  • ViewModel과 바인딩하여 데이터를 동기화.

3. ViewModel

  • View와 Model 간의 중간 역할.
  • 데이터 바인딩을 통해 View와 통신.
  • Model의 상태를 관찰하고 변화가 있을 때 View에 반영.

MVVM의 특징

  1. 양방향 데이터 바인딩:
    • View와 ViewModel 간에 데이터가 자동으로 동기화.
    • UI 변경이 데이터에, 데이터 변경이 UI에 즉시 반영.
  2. View와 ViewModel의 독립성:
    • ViewModel은 View에 직접 접근하지 않으므로 높은 재사용성 제공.
  3. 테스트 용이성:
    • ViewModel이 UI와 분리되어 있어 독립적인 단위 테스트 가능.

장점

  1. 코드 재사용성 증가:
    • ViewModel과 Model을 재사용 가능.
  2. 생산성 증가:
    • 데이터 바인딩을 통해 UI 업데이트가 간소화.
  3. 유지보수성 향상:
    • View와 ViewModel이 분리되어 유지보수가 용이.
  4. 테스트 가능성:
    • UI 논리가 ViewModel에 집중되므로 테스트가 쉬움.

단점

  1. 복잡성 증가:
    • 데이터 바인딩을 설정하고 관리하는 것이 어려울 수 있음.
  2. 초기 학습 곡선:
    • MVVM에 익숙하지 않은 개발자에게는 각 구성 요소의 역할을 이해하는 데 시간이 걸림.
  3. 성능 문제:
    • 데이터 바인딩이 과도하게 사용되면 성능 저하를 유발할 수 있음.

적용 예시

1. Todo 리스트 프로그램

  • Model: 할 일 데이터(텍스트, 완료 여부 등)를 저장하고 관리.
  • View: 사용자에게 할 일 목록을 표시하고, 추가/삭제/완료 버튼을 제공.
  • ViewModel: 사용자 입력(추가, 삭제)을 처리하고, Model의 변경 사항을 View에 반영.

2. 사용자 정보 관리

  • Model: 사용자 정보(이름, 이메일 등)를 저장.
  • View: 사용자 정보 입력 폼과 출력 화면.
  • ViewModel: 입력 데이터 검증 및 Model 업데이트를 처리하고, 결과를 View에 바인딩.

3. 계산기 프로그램

  • Model: 계산 로직.
  • View: 입력 필드, 결과 화면, 계산 버튼.
  • ViewModel: 입력 값에 따라 Model에 연산 요청을 하고, 결과를 View에 바인딩.

C++ 예제 코드: 간단한 Todo 리스트 프로그램

#include <iostream>
#include <vector>
#include <string>
#include <functional>
using namespace std;

// Model: 할 일 데이터 관리
class TodoModel {
private:
    vector<string> todos;

public:
    void addTodo(const string& todo) {
        todos.push_back(todo);
    }

    void removeTodo(int index) {
        if (index >= 0 && index < todos.size()) {
            todos.erase(todos.begin() + index);
        }
    }

    vector<string> getTodos() const {
        return todos;
    }
};

// ViewModel: View와 Model 간의 중간 역할
class TodoViewModel {
private:
    TodoModel& model;
    function<void()> onUpdate;

public:
    TodoViewModel(TodoModel& model) : model(model) {}

    void setOnUpdateCallback(function<void()> callback) {
        onUpdate = callback;
    }

    void addTodo(const string& todo) {
        model.addTodo(todo);
        if (onUpdate) onUpdate();
    }

    void removeTodo(int index) {
        model.removeTodo(index);
        if (onUpdate) onUpdate();
    }

    vector<string> getTodos() const {
        return model.getTodos();
    }
};

// View: 사용자 인터페이스
class TodoView {
private:
    TodoViewModel& viewModel;

public:
    TodoView(TodoViewModel& viewModel) : viewModel(viewModel) {
        viewModel.setOnUpdateCallback([this]() { render(); });
    }

    void render() {
        system("clear"); // 화면 지우기 (Linux/Unix 기준)
        cout << "Todo List:" << endl;
        auto todos = viewModel.getTodos();
        for (size_t i = 0; i < todos.size(); ++i) {
            cout << i + 1 << ". " << todos[i] << endl;
        }
        cout << endl;
    }

    void showMenu() {
        while (true) {
            cout << "1. Add Todo" << endl;
            cout << "2. Remove Todo" << endl;
            cout << "3. Exit" << endl;
            cout << "Choose an option: ";

            int choice;
            cin >> choice;
            cin.ignore(); // 버퍼 비우기

            if (choice == 1) {
                cout << "Enter a new todo: ";
                string todo;
                getline(cin, todo);
                viewModel.addTodo(todo);
            } else if (choice == 2) {
                cout << "Enter the number of the todo to remove: ";
                int index;
                cin >> index;
                viewModel.removeTodo(index - 1);
            } else if (choice == 3) {
                break;
            } else {
                cout << "Invalid choice. Try again." << endl;
            }
        }
    }
};

// Main: 애플리케이션 실행
int main() {
    TodoModel model;
    TodoViewModel viewModel(model);
    TodoView view(viewModel);

    view.render();
    view.showMenu();

    return 0;
}

코드 설명

Model

  • TodoModel: 할 일 데이터를 저장하고 관리합니다.

ViewModel

  • TodoViewModel: 사용자 입력을 처리하고 Model을 업데이트합니다.
  • Model에서 변경된 데이터를 View에 알립니다.

View

  • TodoView: 사용자에게 할 일 목록을 표시하고, 사용자 입력을 받아 ViewModel에 전달합니다.

실행 결과 예시

Todo List:

1. Buy groceries
2. Finish homework
3. Call a friend

1. Add Todo
2. Remove Todo
3. Exit
Choose an option: 2
Enter the number of the todo to remove: 1

Todo List:

1. Finish homework
2. Call a friend

1. Add Todo
2. Remove Todo
3. Exit
Choose an option: 3

MVVM 패턴 요약

  1. View는 UI 작업만 수행하며 ViewModel과 데이터를 바인딩.
  2. ViewModel은 비즈니스 로직과 데이터를 관리하고 View와 통신.
  3. Model은 데이터를 저장하고 처리.

MVVM은 특히 데이터 바인딩을 제공하는 프레임워크에서 강력한 패턴으로, 유지보수성과 테스트 용이성을 크게 향상시킵니다.