MVC Design Pattern

MVC Design Pattern

Posted by Gandis on October 17, 2019

MVC Design Pattern

MVC는 Model View Controller의 약자로 소프트웨어 디자인 패턴을 말한다. 소프트웨어에 디자인 패턴을 적용하는 이유는, 디자인 패턴을 잘 적용할 경우 향후 지속적으로 개발하고 유지관리를 하는데 드는 비용을 줄일 수 있기 때문이다. 대부분의 디자인 패턴은 사용자의 인터페이스(UI)와 비지니스 로직, 그리고 Data를 분리하여 관리함으로써 개발 및 유지관리의 효율성을 높일 수 있다.

MVC 패턴이 정확히 어떻게 구성되는지에 대한 의견이 분분하다. 검색하여 나오는 MVC 패턴 다이어그램만 보아도 제각각으로 그려진 이미지가 많이 보인다.

핵심은 Model과 View는 분리되어 있으며, Model은 View와 Controller의 존재를 알지 못하고, View는 Model과 Controller의 존재를 알지 못한다.

아래 그림은 옵저버 패턴을 적용하였을 때의 MVC 다이어그램을 나타낸다. 옵저버 패턴에 대해서는 나중에 언급하고 아래 그림의 관계에 살펴보며 MVC패턴을 알아 보자.

MVC 구조

Model

Model은 데이터에 해당된다. 즉, 소프트웨어에서 사용되는 모든 데이터에 해당한다. MVC 패턴에서 Model은 데이터가 변경될 경우, 데이터가 변경되었음 View에게 알리는 역할을 해야한다. 위 그림에서 Model에 연결된 선중에 View를 향하는 점선을 확인 할 수 있다. 바로 점선이 Model이 View에게 데이터가 변경되었음을 알리는 Event를 나타낸다. Controller가 Model의 데이터를 변경한 후, 읽어와 View를 업데이트 하는경우도 있으나, 여기서는 Model이 Event로 View를 업데이트 하는 방식으로 한다.

반대로 굵은 선은 Controller에서 Model을 향하고 있다. 이는 Controller는 Model과 직접적으로 연결되서 Model을 변경할 수 있음을 나타낸다. 하지만 Model에서 Controller또는 View로 향하는 굵은 선이 없다. 이는 Model이 Controller와 View와 직접적으로 연결되어서는 안되며, Controller와 View의 존재를 알아서도 안된다는 것을 의미한다.

View

View는 Model의 데이터를 사용자에게 보여주는 역할을 한다. Model의 데이터를 사용자에게 표시하기 위해서는 Model의 Data를 가지고 있어야 할것 같지만 절대 그래서는 안된다. 오로지 Model과의 관계는 오직 Event(옵저버 또는 리스너)를 통해서 데이터만 전달 받고, 전달받은 데이터는 표시만 해야 한다.

또한 View는 사용자에게 전달받은 Event를 Controller에게 통보해야 한다. 위 그림에서 View에서 Controller를 향하는 점선을 볼 수 있는데, 바로 사용자로부터 전달받은 Event를 Controller에게 전달하는 과정이다. 마찬가지로 View는 Controller에 입력 정보를 전달만 할 뿐, Controller의 존재를 알아서는 안된다.

Controller

Controller는 Model과 View를 연결하는 다리 역할을 한다. Controller는 View로 부터 전달받은 입력 정보를 처리하는 역할을 한다. 따라서 Controller에는 메인 로직이 포함되어 있으며, Model의 정보를 변경하기도 하고, Model의 정보를 읽어와 View에 표시하는 등의 전반적인 동작을 제어한다. Controller는 전반적인 동작을 제어하기 때문에 View와 Model의 정보를 알고 있어야 한다.

MVC 패턴의 장점

- MVC 패턴을 모델과 뷰를 분리해 준다.

MVC 패턴의 단점

- Controller가 뷰와 밀접한 관련이 있으며, 뷰일수도 있다. 그렇기 때문에 framework의 API에 종속되어 있어 유닛 테스트를 하기가 어렵다. - Controller가 대부분의 비지니스 로직을 가지고 있기 때문에 시간이 지남에 따라 Controller의 코드양이 어마어마 해질 수 있다.

MVC 패턴 구현

아래는 데이터를 List에 추가 또는 삭제하는 매우 단순한 어플리케이션을 MVC 패턴으로 구현한 코드이다. QT Framework에서 C++를 사용하여 구현되었다.

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "maincontroller.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MainController mainWindow;
    mainWindow.show();

    return a.exec();
}

maincontroller.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "clistmodel.h"

namespace Ui {
class MainWindow;
}

class MainController : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainController(QWidget *parent = 0);
    ~MainController();

private:
    Ui::MainWindow *ui;
    CListModel m_lstModel;
};

maincontroller.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "maincontroller.h"
#include "ui_mainwindow.h"

MainController::MainController(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_lstModel.setListListener(ui->listWidget);

    connect(ui->btnNew, &QPushButton::clicked, this, [&](bool bState){ Q_UNUSED(bState); m_lstModel.append(ui->editNew->text());});
    connect(ui->btnDelete, &QPushButton::clicked, this, [&](bool bState){ Q_UNUSED(bState); m_lstModel.removeAt(ui->listWidget->currentIndex().row());});
}

MainController::~MainController()
{
    delete ui;
}

listview.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef CLISTVIEW_H
#define CLISTVIEW_H

#include <QListWidget>

#include "ilistlistener.h"

class CListView : public QListWidget,  public IListListner
{
public:
    explicit CListView(QWidget *parent = NULL);
    ~CListView();

    void OnAdded(const QString &strNew) override;
    void OnDeleted(const int &nIndex) override;
};

#endif // CLISTVIEW_H

listview.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "clistview.h"

CListView::CListView(QWidget *parent)
    : QListWidget(parent)
{
    // do nothing.
}

CListView::~CListView()
{
    // do nothing.
}

void CListView::OnAdded(const QString &strNew)
{
    addItem(strNew);
}

void CListView::OnDeleted(const int &nIndex)
{
    model()->removeRow(nIndex);
}

listmodel.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef CLISTMODEL_H
#define CLISTMODEL_H

#include <QList>
#include "ilistlistener.h"

class CListModel : public QList<QString>
{
public:
    explicit CListModel();
    virtual ~CListModel();

    virtual void append(const QString &strNew);
    virtual void removeAt(int nIndex);

    void setListListener(IListListner *pListListener);

private:
    IListListner *m_pListListener;
};

#endif // CLISTMODEL_H

listmodel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "clistmodel.h"

CListModel::CListModel()
    : m_pListListener(NULL)
{
    // do nothing.
}

CListModel::~CListModel()
{
    // do nothing.
}

void CListModel::setListListener(IListListner *pListListener)
{
    m_pListListener = pListListener;
}

void CListModel::append(const QString &strNew)
{
    QList::append(strNew);

    if(m_pListListener != NULL)
        m_pListListener->OnAdded(strNew);
}

void CListModel::removeAt(int nIndex)
{
    QList::removeAt(nIndex);

    if(m_pListListener != NULL)
        m_pListListener->OnDeleted(nIndex);
}

소스코드

위에서 예제로 구현한 풀소스코드는 아래 위치에서 확인 할 수 있다.

Full Source Code

Reference

  • https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
  • https://m.blog.naver.com/jhc9639/220967034588
  • https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/