소프트웨어 디자인 패턴 - 2. MVP 패턴 (Model-View-Presenter)

2025. 1. 19. 23:52소프트웨어/디자인 패턴

MVP 패턴이란?

MVP(Model-View-Presenter) 패턴은 애플리케이션을 Model, View, Presenter로 나누어 설계하는 소프트웨어 디자인 패턴입니다. MVC(Model-View-Controller) 패턴과 유사하지만, Presenter와 View가 1:1 관계를 가지며 Presenter가 View와 Model 사이의 모든 로직을 처리한다는 점에서 차이가 있습니다. 이 패턴은 UI 코드와 비즈니스 로직을 명확히 분리하여 유지보수성과 테스트 용이성을 향상시킵니다.


구성 요소

1. Model

  • 데이터를 관리하고 저장.
  • 데이터베이스와 상호작용하거나 상태를 유지.
  • MVC의 Model과 동일한 역할을 수행.

2. View

  • 사용자 인터페이스(UI)를 담당.
  • Presenter로부터 데이터를 받아 화면에 표시.
  • 사용자 입력을 Presenter에 전달.
  • 비즈니스 로직 없이 UI 관련 작업에만 집중.

3. Presenter

  • View와 Model 사이의 중재자 역할.
  • 비즈니스 로직을 수행하고, Model에서 데이터를 가져와 View에 전달.
  • View와 1:1로 연결되어 직접 데이터를 업데이트.

MVP의 특징

  1. View와 Presenter의 1:1 관계:
    • 각 View는 하나의 Presenter와만 연결됩니다.
  2. View는 단순히 UI 역할만 수행:
    • View는 데이터 처리 로직 없이 사용자 입력 및 UI 표시만 담당합니다.
  3. 테스트 용이성 증가:
    • Presenter가 View와 분리되어 단위 테스트가 쉽습니다.

장점

  1. 유지보수 용이:
    • View와 로직이 분리되어 수정이 다른 구성 요소에 영향을 미치지 않습니다.
  2. 테스트 용이성:
    • Presenter가 View와 분리되어 단위 테스트가 쉽습니다.
  3. 높은 재사용성:
    • 동일한 Presenter가 다양한 View와 결합될 수 있어 코드 재사용성이 증가합니다.
  4. UI 독립성:
    • View를 변경해도 Presenter와 Model에는 영향을 미치지 않아 독립적으로 UI를 개선할 수 있습니다.

단점

  1. 복잡성 증가:
    • MVC보다 더 많은 클래스와 파일이 필요하므로 코드베이스가 커질 수 있습니다.
  2. View와 Presenter 간 강한 결합:
    • 1:1 관계로 인해 Presenter와 View의 재사용성이 제한될 수 있습니다.
  3. Presenter 비대화 문제:
    • 시간이 지남에 따라 Presenter에 로직이 누적되어 비대해질 위험이 있습니다.
  4. 초기 학습 곡선:
    • MVP 패턴에 익숙하지 않은 개발자는 각 구성 요소의 역할을 이해하는 데 시간이 걸릴 수 있습니다.

적용 예시

1. 계산기 프로그램

  • Model: 덧셈, 뺄셈, 곱셈, 나눗셈 등의 연산 로직.
  • View: 숫자와 결과를 화면에 표시하고 버튼 입력 처리.
  • Presenter: 사용자의 입력을 Model로 전달하고, 연산 결과를 View에 전달.

2. 로그인 시스템

  • Model: 사용자 인증 데이터 관리.
  • View: 사용자 입력 필드(아이디/비밀번호)와 결과 메시지 표시.
  • Presenter: 입력 데이터를 검증하고, Model과 상호작용하여 결과를 View에 전달.

3. 상품 관리 프로그램

  • Model: 상품 데이터 저장 및 조회 로직.
  • View: 상품 목록을 표시하고, 사용자 입력을 처리.
  • Presenter: 사용자의 요청에 따라 Model을 업데이트하고, 결과를 View로 전달.

C++ 예제 코드: 간단한 계산기 프로그램

#include <iostream>
#include <stdexcept>
#include <limits> // numeric_limits 사용을 위해 필요
using namespace std;

// Model: 계산 로직 관리
class CalculatorModel {
public:
    double add(double a, double b) {
        return a + b;
    }

    double subtract(double a, double b) {
        return a - b;
    }

    double multiply(double a, double b) {
        return a * b;
    }

    double divide(double a, double b) {
        if (b == 0) {
            throw invalid_argument("Division by zero is not allowed.");
        }
        return a / b;
    }
};

// View: 사용자 인터페이스 관리
class CalculatorView {
public:
    void displayResult(double result) {
        cout << "Result: " << result << endl;
    }

    void displayError(const string& message) {
        cout << "Error: " << message << endl;
    }

    double getUserInput(const string& prompt) {
        cout << prompt;
        double input;
        while (!(cin >> input)) {
            cin.clear(); // 입력 오류 복구
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
            cout << "Invalid input. Please enter a number: ";
        }
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        return input;
    }

    char getUserOperation() {
        clearScreen();
        cout << "Enter operation (+, -, *, /): ";
        char op;
        while (!(cin >> op) || (op != '+' && op != '-' && op != '*' && op != '/')) {
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
            cout << "Invalid operation. Please enter +, -, *, or /: ";
        }
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        return op;
    }

private:
    void clearScreen() {
#ifdef _WIN32
        system("cls");
#else
        system("clear");
#endif
    }
};

// Presenter: 로직 처리 및 View와 Model 연결
class CalculatorPresenter {
private:
    CalculatorModel& model;
    CalculatorView& view;

public:
    CalculatorPresenter(CalculatorModel& model, CalculatorView& view)
        : model(model), view(view) {}

    void performCalculation() {
        view.clearScreen();
        try {
            double a = view.getUserInput("Enter first number: ");
            double b = view.getUserInput("Enter second number: ");
            char op = view.getUserOperation();

            double result;
            switch (op) {
                case '+':
                    result = model.add(a, b);
                    break;
                case '-':
                    result = model.subtract(a, b);
                    break;
                case '*':
                    result = model.multiply(a, b);
                    break;
                case '/':
                    result = model.divide(a, b);
                    break;
                default:
                    throw invalid_argument("Invalid operation.");
            }

            view.displayResult(result);
        } catch (const exception& e) {
            view.displayError(e.what());
        }
    }
};

// Main: 애플리케이션 실행
int main() {
    CalculatorModel model;
    CalculatorView view;
    CalculatorPresenter presenter(model, view);

    bool continueCalculation = true;
    while (continueCalculation) {
        presenter.performCalculation();
        cout << "Do you want to perform another calculation? (y/n): ";
        char choice;
        while (!(cin >> choice) || (tolower(choice) != 'y' && tolower(choice) != 'n')) {
            cin.clear();
            cin.ignore(numeric_limits<streamsize>::max(), '\n');
            cout << "Invalid input. Please enter 'y' or 'n': ";
        }
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        continueCalculation = (tolower(choice) == 'y');
    }

    return 0;
}

코드 설명

Model

  • 계산 로직(add, subtract, multiply, divide)을 관리.

View

  • 사용자 입력을 받고 결과를 출력.
  • Presenter로부터 데이터를 받아 사용자에게 표시.

Presenter

  • 사용자 입력에 따라 Model과 상호작용.
  • Model에서 계산 결과를 받아 View에 전달.
  • 예외 처리를 통해 에러 메시지를 View에 전달.

실행 결과 예시

Enter first number: 10
Enter second number: 5
Enter operation (+, -, *, /): +
Result: 15
Do you want to perform another calculation? (y/n): y
Enter first number: 10
Enter second number: 0
Enter operation (+, -, *, /): /
Error: Division by zero is not allowed.
Do you want to perform another calculation? (y/n): n

MVP 패턴 요약

  1. View는 UI 작업만 수행하고 로직을 Presenter에 위임.
  2. Presenter는 비즈니스 로직을 처리하며 Model과 View를 연결.
  3. Model은 데이터를 관리하고 저장.

MVP 패턴은 UI와 로직을 철저히 분리해야 하는 애플리케이션에서 특히 적합하며, 테스트와 유지보수의 편리함을 제공합니다.