소프트웨어 디자인 패턴

디자인 패턴. MVP (Model-View-Presenter)

developer-tj 2023. 3. 10. 12:00
반응형

MVP (Model-View-Presenter) 패턴은 애플리케이션을 구성하는 구성 요소를 세 가지로 나누는 소프트웨어 아키텍처 패턴입니다.

MVP 패턴의 구성요소는 다음과 같습니다.

  1. Model
    • 데이터와 비즈니스 로직을 포함하고 있는 컴포넌트
    • 데이터와 비즈니스 로직에 대한 수정은 Presenter에서 수행됩니다.
  2. View
    • 사용자 인터페이스를 담당하는 컴포넌트
    • 사용자 입력을 Presenter에 전달합니다.
    • Presenter로부터 데이터를 전달 받아 표시합니다.
  3. Presenter
    • View와 Model 사이에서 조정하는 컴포넌트
    • View로부터 입력을 수신하여 Model을 업데이트하고, Model의 변화를 View에 반영합니다.
    • View와 Model 사이의 인터페이스를 정의합니다.

MVP 패턴의 핵심 아이디어는 View와 Model 사이에 Presenter라는 중간 매개체를 두어 View와 Model을 분리하는 것입니다.
이렇게 하면 View와 Model 간에 의존성이 줄어들고, 각 컴포넌트를 독립적으로 개발하고 테스트할 수 있습니다.

MVP 패턴의 흐름은 다음과 같습니다.

  1. 사용자가 View를 조작합니다.
  2. View는 Presenter에 사용자 입력을 전달합니다.
  3. Presenter는 Model을 업데이트하고, 변경된 데이터를 View에 반영합니다.
  4. View는 Presenter로부터 전달받은 데이터를 이용하여 화면을 갱신합니다.

MVP 패턴은 UI와 비즈니스 로직의 분리로 인해 코드의 가독성과 유지보수성이 증가하며, 뷰와 모델 간의 결합도를 낮추어 애플리케이션의 확장성을 높이는 장점이 있습니다.

간단한 MVP 패턴의 예제 코드입니다.


#include <iostream>
#include <vector>

// Model
class Data {
public:
    void add(int value) {
        values.push_back(value);
    }

    int get(int index) const {
        return values[index];
    }

    int size() const {
        return values.size();
    }

private:
    std::vector values;
};

// Presenter
class Presenter {
public:
    Presenter(Data& data)
        : data(data)
    {}

    void add(int value) {
        data.add(value);
        viewShowData();
    }

    void setViewShowDataFunction(void (*viewShowDataFunc)(const Data&)) {
        viewShowData = viewShowDataFunc;
    }

private:
    Data& data;
    void (*viewShowData)(const Data&) = nullptr;
};

// View
class View {
public:
    void showData(const Data& data) const {
        std::cout << "Data: ";
        for (int i = 0; i < data.size(); ++i) {
            std::cout << data.get(i) << " ";
        }
        std::cout << std::endl;
    }
};

// Main
int main() {
    Data data;
    View view;
    Presenter presenter(data);

    presenter.setViewShowDataFunction([&view](const Data& data) {
        view.showData(data);
    });

    presenter.add(1);
    presenter.add(2);
    presenter.add(3);

    return 0;
}

위의 예제 코드에서 Model은 Data 클래스로 구현되어 있습니다.
Data 클래스는 int형 값들을 저장하는 std::vector를 멤버로 가지고 있으며, 값을 추가하는 add(), 값을 가져오는 get(), 데이터 크기를 반환하는 size() 함수를 제공합니다.

Presenter는 Presenter 클래스로 구현되어 있습니다.
Presenter 클래스는 Data 객체를 받아서, Data에 값을 추가하는 add() 함수를 제공합니다. add() 함수는 값을 추가한 후, viewShowData() 함수를 호출하여 데이터를 출력합니다.

View는 View 클래스로 구현되어 있습니다.
View 클래스는 Data 객체를 받아서 출력하는 showData() 함수를 제공합니다.

위 예제 코드에서는 Main 함수에서 Data, View, Presenter 객체를 생성하여 Presenter의 add() 함수를 호출하여 데이터를 추가하고, Presenter의 setViewShowDataFunction() 함수를 사용하여 View의 showData() 함수와 Presenter의 viewShowData() 함수를 연결합니다.
이를 통해 Presenter는 데이터를 처리하면서 View와 직접적인 연결 없이 출력을 요청하게 됩니다.

위의 예제 코드는 사용자의 입력값을 받지 않고, Presenter에서 직접 Model에 값을 추가하는 간단한 예제입니다.


MVP 패턴에서는 View에서 사용자의 입력을 받아 Presenter에 전달하고, Presenter는 Model을 업데이트하고 View에 데이터를 업데이트하도록 요청합니다.

아래는 입력 값을 받아 데이터를 추가하는 예제 코드입니다.


#include <iostream>
#include <vector>

// Model
class Data {
public:
    void add(int value) {
        values.push_back(value);
    }

    int get(int index) const {
        return values[index];
    }

    int size() const {
        return values.size();
    }

private:
    std::vector values;
};

// Presenter
class Presenter {
public:
    Presenter(Data& data)
        : data(data)
    {}

    void add(int value) {
        data.add(value);
        viewShowData();
    }

    void setViewShowDataFunction(void (*viewShowDataFunc)(const Data&)) {
        viewShowData = viewShowDataFunc;
    }

private:
    Data& data;
    void (*viewShowData)(const Data&) = nullptr;
};

// View
class View {
public:
    int getUserInput() const {
        int value;
        std::cout << "Enter a value: ";
        std::cin >> value;
        return value;
    }

    void bindPresenter(Presenter& presenter) {
        userInputFunction = std::bind(&Presenter::add, &presenter, std::placeholders::_1);
    }

    void start() {
        while (true) {
            int value = getUserInput();
            userInputFunction(value);
        }
    }

private:
    std::function<void(int)> userInputFunction = nullptr;
};

// Main
int main() {
    Data data;
    View view;
    Presenter presenter(data);

    presenter.setViewShowDataFunction([&view](const Data& data) {
        view.showData(data);
    });

    view.bindPresenter(presenter);
    view.start();

    return 0;
}

Presenter 클래스와 Data 클래스는 이전 예제 코드와 동일합니다.
View 클래스와 main() 함수만 변경되었습니다.
위의 예제에서는 View에서 사용자 입력을 받아 bindPresenter 함수를 사용하여 Presenter와 연결시켜주고, start 함수를 실행하여 사용자의 입력을 지속적으로 받습니다.