---
title: "무엇을 쪼개느냐가 아니라 누가 누구를 아는가"
description: "셋 다 데이터와 화면을 나누는 이야기입니다. 다른 것은 나누는 방법이 아니라 나눈 뒤에 남는 참조의 방향입니다."
author: "유니얼"
pubDate: 2026-09-09
category: "game-dev/design-patterns"
tags: ["Unity"]
canonical: https://blog.unialgames.com/posts/unity-mvc-mvp-mvvm/
---
체력바를 만듭니다. 처음에는 이렇게 씁니다.

```csharp
public class HealthBar : MonoBehaviour
{
    [SerializeField] Image fill;
    [SerializeField] Text label;

    int hp = 100;

    public void TakeDamage(int amount)
    {
        hp = Mathf.Clamp(hp - amount, 0, 100);   // 게임 규칙
        fill.fillAmount = hp / 100f;             // 화면
        label.text = $"{hp} / 100";              // 화면
        fill.color = hp <= 30 ? Color.red : Color.green;
    }
}
```

동작합니다. 그런데 이 클래스에는 두 가지가 섞여 있습니다.
**체력이 0 아래로 안 내려간다는 게임 규칙**과 **체력을 어떻게 보여줄지**입니다.

섞여 있으면 이런 일이 생깁니다. 체력바 UI 를 새로 디자인하면 게임 규칙도 같이 옮겨야 하고,
체력 계산을 시험해 보려면 캔버스와 이미지를 전부 세워야 합니다.
멀티플레이가 붙어서 서버가 체력을 관리하게 되면 이 클래스는 통째로 못 씁니다.

MVC, MVP, MVVM 은 전부 **이 둘을 어떻게 떼어 놓을 것인가**에 대한 답입니다.

## 셋 다 같은 세 조각으로 나눕니다

| 조각 | 하는 일 | 하지 않는 일 |
| --- | --- | --- |
| **Model** | 데이터와 그 데이터의 규칙 | 화면을 모른다 |
| **View** | 보여 주기와 입력 받기 | 게임 규칙을 모른다 |
| **중간자** | 둘을 잇기 | 데이터를 보관하지 않는다 |

여기까지는 셋이 완전히 같습니다. 그래서 **무엇을 쪼개느냐로는 셋이 구분되지 않습니다.**
갈리는 것은 나눈 다음입니다. **누가 누구를 아는가.**

<figure class="diagram">
<svg viewBox="0 0 680 250" role="img" aria-label="MVC 는 셋이 서로 얽히고 MVP 는 중간자가 유일한 통로이며 MVVM 은 뷰와 중간자 사이가 바인딩으로 느슨해진다는 비교 그림">
<text x="88" y="24" font-size="13" text-anchor="middle" fill="var(--text-muted)">MVC</text>
<text x="330" y="24" font-size="13" text-anchor="middle" fill="var(--text-muted)">MVP</text>
<text x="572" y="24" font-size="13" text-anchor="middle" fill="var(--text-muted)">MVVM</text>
<g stroke="var(--border)" fill="var(--bg)">
<rect x="30" y="42" width="52" height="28" rx="6"></rect>
<rect x="94" y="42" width="52" height="28" rx="6"></rect>
<rect x="62" y="120" width="52" height="28" rx="6"></rect>
<rect x="272" y="42" width="52" height="28" rx="6"></rect>
<rect x="336" y="42" width="52" height="28" rx="6"></rect>
<rect x="304" y="120" width="52" height="28" rx="6"></rect>
<rect x="514" y="42" width="52" height="28" rx="6"></rect>
<rect x="578" y="42" width="52" height="28" rx="6"></rect>
<rect x="546" y="120" width="52" height="28" rx="6"></rect>
</g>
<g font-size="11" text-anchor="middle" fill="var(--text-muted)">
<text x="56" y="61">Model</text>
<text x="120" y="61">View</text>
<text x="88" y="139">Controller</text>
<text x="298" y="61">Model</text>
<text x="362" y="61">View</text>
<text x="330" y="139">Presenter</text>
<text x="540" y="61">Model</text>
<text x="604" y="61">View</text>
<text x="572" y="139">ViewModel</text>
</g>
<g stroke="var(--warn)" stroke-width="1.4" fill="none">
<path d="M82 56 L94 56"></path>
</g>
<g stroke="var(--text-muted)" stroke-width="1.3" fill="none">
<path d="M62 74 L76 118"></path>
<path d="M114 74 L100 118"></path>
<path d="M304 74 L318 118"></path>
<path d="M356 74 L342 118"></path>
<path d="M546 74 L560 118"></path>
</g>
<g stroke="var(--accent)" stroke-width="1.4" stroke-dasharray="4 3" fill="none">
<path d="M598 74 L584 118"></path>
</g>
<text x="88" y="176" font-size="12" text-anchor="middle" fill="var(--warn)">셋이 서로 안다</text>
<text x="88" y="196" font-size="11" text-anchor="middle" fill="var(--text-muted)">Model 과 View 가</text>
<text x="88" y="212" font-size="11" text-anchor="middle" fill="var(--text-muted)">직접 붙어 있다</text>
<text x="330" y="176" font-size="12" text-anchor="middle" fill="var(--text)">가운데만 안다</text>
<text x="330" y="196" font-size="11" text-anchor="middle" fill="var(--text-muted)">Model 과 View 는</text>
<text x="330" y="212" font-size="11" text-anchor="middle" fill="var(--text-muted)">서로를 모른다</text>
<text x="572" y="176" font-size="12" text-anchor="middle" fill="var(--accent)">가운데도 약하게</text>
<text x="572" y="196" font-size="11" text-anchor="middle" fill="var(--text-muted)">점선은 바인딩이다</text>
<text x="572" y="212" font-size="11" text-anchor="middle" fill="var(--text-muted)">갱신은 시스템이 한다</text>
</svg>
<figcaption>왼쪽에서 오른쪽으로 갈수록 남아 있는 참조가 줄어듭니다.</figcaption>
</figure>

## MVC: 셋이 서로 얽힌다

MVC 다이어그램이 어떤 자료에서는 삼각형이고 어떤 자료에서는 사각형인 이유는
**사용자를 그림에 넣느냐 빼느냐** 차이일 뿐입니다. 중요한 건 그게 아니라
**Model 과 View 가 직접 붙어 있다**는 점입니다.

Model 이 바뀌면 View 가 알아야 하니 View 안에 그걸 받는 코드가 생깁니다.
Controller 도 View 를 직접 만집니다. 결국 셋이 서로를 알게 됩니다.

<x-wiring>
<script type="application/json">
{
  "label": "MVC 의 흐름",
  "width": 620,
  "height": 300,
  "nodes": [
    {
      "id": "user",
      "title": "사용자",
      "x": 18,
      "y": 124,
      "w": 96,
      "h": 46,
      "aside": true
    },
    {
      "id": "c",
      "title": "Controller",
      "sub": "입력을 받아 시킨다",
      "x": 168,
      "y": 24,
      "w": 150,
      "h": 54
    },
    {
      "id": "m",
      "title": "Model",
      "sub": "데이터와 규칙",
      "x": 420,
      "y": 124,
      "w": 150,
      "h": 54
    },
    {
      "id": "v",
      "title": "View",
      "sub": "보여 준다",
      "x": 168,
      "y": 222,
      "w": 150,
      "h": 54
    }
  ],
  "edges": [
    {
      "from": "user",
      "to": "c",
      "label": "누른다",
      "bend": 0
    },
    {
      "from": "c",
      "to": "m",
      "label": "바꿔라",
      "bend": 0
    },
    {
      "from": "m",
      "to": "v",
      "label": "바뀌었다",
      "bend": 0
    },
    {
      "from": "v",
      "to": "user",
      "label": "보인다",
      "bend": 0
    },
    {
      "from": "c",
      "to": "v",
      "label": "직접 만지기도 한다",
      "bend": 0
    }
  ],
  "steps": [
    {
      "edge": 0,
      "why": "사용자가 버튼을 누릅니다. 입력은 Controller 가 받습니다."
    },
    {
      "edge": 1,
      "why": "Controller 가 Model 에게 시킵니다. 여기까지는 셋 다 같습니다."
    },
    {
      "edge": 2,
      "why": "Model 이 View 에게 직접 알립니다. 이 선이 MVC 를 MVC 로 만듭니다. Model 과 View 가 서로를 압니다."
    },
    {
      "edge": 3,
      "why": "View 가 화면을 갱신합니다."
    },
    {
      "edge": 4,
      "why": "게다가 Controller 도 View 를 직접 만집니다. 결국 셋이 서로를 알게 됩니다."
    }
  ],
  "note": "View 로 들어오는 선이 둘입니다. Model 쪽에서도 오고 Controller 쪽에서도 옵니다."
}
</script>
</x-wiring>

유니티에서 MVC 를 잘 안 쓰는 이유가 이것입니다.
View 자체는 문제가 없습니다. uGUI 가 잘 만들어져 있으니까요.
**Model 의 변경을 받으려고 View 쪽에 계속 코드가 붙는 것**이 문제입니다.

## MVP: 가운데가 유일한 통로

그래서 유니티에서는 대개 MVP 를 씁니다. 규칙은 두 줄입니다.

> **Model 은 View 를 모른다. View 는 Model 을 모른다.**

Model 은 데이터와 규칙만 갖고, 바뀌면 알리기만 합니다.

```csharp
public class HealthModel
{
    public event Action<int> OnChanged;

    int current;
    readonly int max;

    public HealthModel(int max) { this.max = max; current = max; }

    public int Current
    {
        get => current;
        private set
        {
            var clamped = Mathf.Clamp(value, 0, max);   // 규칙은 여기 있다
            if (clamped == current) return;
            current = clamped;
            OnChanged?.Invoke(current);
        }
    }

    public void TakeDamage(int amount) => Current -= amount;
}
```

View 는 시키는 대로 보여 주기만 합니다. 게임 규칙은 없습니다.

```csharp
public class HealthView : MonoBehaviour
{
    [SerializeField] Image fill;
    [SerializeField] Text label;
    public Button DamageButton;

    public void SetFill(float ratio) => fill.fillAmount = ratio;
    public void SetText(string s) => label.text = s;
    public void SetColor(Color c) => fill.color = c;
}
```

Presenter 가 둘을 잇습니다.

```csharp
public class HealthPresenter : MonoBehaviour
{
    [SerializeField] HealthView view;
    HealthModel model;

    void Awake() => model = new HealthModel(100);

    void OnEnable()
    {
        model.OnChanged += Render;
        view.DamageButton.onClick.AddListener(() => model.TakeDamage(10));
        Render(model.Current);
    }

    void OnDisable()
    {
        model.OnChanged -= Render;                    // 짝을 반드시 맞춘다
        view.DamageButton.onClick.RemoveAllListeners();
    }

    void Render(int hp)
    {
        view.SetText($"{hp} / 100");
        view.SetFill(hp / 100f);
        view.SetColor(Color.Lerp(Color.red, Color.green, hp / 100f));
    }
}
```

이제 체력 계산은 캔버스 없이 시험할 수 있습니다. `HealthModel` 은 그냥 C# 클래스입니다.
UI 디자인을 갈아엎어도 `HealthModel` 은 그대로입니다.

`OnChanged` 는 옵저버입니다. 알리는 쪽이 누가 듣는지 모르는 구조요.
**UI 아키텍처의 바닥에는 항상 이게 깔려 있습니다.**

한 바퀴가 어떻게 도는지 보면 이렇습니다.

<x-wiring>
<script type="application/json">
{
  "label": "MVP 의 흐름",
  "width": 620,
  "height": 418,
  "nodes": [
    {
      "id": "user",
      "title": "사용자",
      "x": 250,
      "y": 8,
      "w": 120,
      "h": 40,
      "aside": true
    },
    {
      "id": "v",
      "title": "View",
      "sub": "버튼과 라벨",
      "x": 235,
      "y": 120,
      "w": 150,
      "h": 54
    },
    {
      "id": "p",
      "title": "Presenter",
      "sub": "유일한 통로",
      "x": 235,
      "y": 232,
      "w": 150,
      "h": 54
    },
    {
      "id": "m",
      "title": "Model",
      "sub": "데이터와 규칙",
      "x": 235,
      "y": 344,
      "w": 150,
      "h": 54
    }
  ],
  "edges": [
    {
      "from": "user",
      "to": "v",
      "label": "누른다",
      "bend": 62
    },
    {
      "from": "v",
      "to": "p",
      "label": "클릭이 왔다",
      "bend": 62
    },
    {
      "from": "p",
      "to": "m",
      "label": "TakeDamage(10)",
      "bend": 62
    },
    {
      "from": "m",
      "to": "p",
      "label": "OnChanged(90)",
      "bend": 62
    },
    {
      "from": "p",
      "to": "v",
      "label": "SetFill · SetText",
      "bend": 62
    },
    {
      "from": "v",
      "to": "user",
      "label": "보인다",
      "bend": 62
    }
  ],
  "steps": [
    {
      "edge": 0,
      "why": "사용자가 버튼을 누릅니다."
    },
    {
      "edge": 1,
      "why": "View 는 무슨 뜻인지 모릅니다. Presenter 에게 넘기기만 합니다."
    },
    {
      "edge": 2,
      "why": "Presenter 가 Model 에게 시킵니다. 체력을 0 아래로 안 내리는 규칙은 Model 안에 있습니다."
    },
    {
      "edge": 3,
      "why": "Model 이 바뀌었다고 알립니다. 누가 듣는지는 모릅니다. 이 자리에 옵저버가 깔려 있습니다."
    },
    {
      "edge": 4,
      "why": "Presenter 가 값을 화면 표현으로 바꿔서 View 에 넣습니다. 90 을 \"90 / 100\" 과 색으로 바꾸는 일이 여기 있습니다."
    },
    {
      "edge": 5,
      "why": "화면이 갱신됩니다. 한 바퀴 도는 동안 View 와 Model 을 잇는 선은 하나도 없었습니다."
    }
  ],
  "note": "내려가는 길과 올라오는 길이 모두 가운데를 지납니다. 그게 MVP 입니다."
}
</script>
</x-wiring>

### MVP 가 무거워지는 자리

Presenter 를 다시 보면 `Render` 안에 값을 화면 표현으로 바꾸는 코드가 쌓입니다.
숫자를 문자열로, 비율을 색으로, 구간을 상태 이름으로.
표시 요소가 늘어날수록 이 메서드가 길어지고, 화면이 늘어날수록 이런 Presenter 가 늘어납니다.

그리고 이 두 줄을 매번 손으로 씁니다.

```csharp
model.OnChanged += Render;   // OnEnable
model.OnChanged -= Render;   // OnDisable
```

하나만 빠뜨려도 파괴된 View 를 갱신하려다 예외가 납니다.

## MVVM: 잇는 일을 시스템에 넘긴다

MVVM 은 그 접착제를 사람이 쓰지 않게 합니다. **데이터 바인딩**입니다.

"이 라벨의 텍스트는 저 속성을 따라간다" 를 한 번만 선언해 두면,
속성이 바뀔 때 라벨을 갱신하는 일은 **프레임워크가 합니다.**

```csharp
label.SetBinding(nameof(Label.text), new DataBinding
{
    dataSourcePath = new PropertyPath(nameof(HealthModel.CurrentHealth)),
    bindingMode = BindingMode.ToTarget,
});
```

값을 표현으로 바꾸는 규칙도 컨버터로 한 번 등록해 두면 그 뒤로는 자동입니다.
`Render` 메서드가 통째로 사라집니다.

<x-wiring>
<script type="application/json">
{
  "label": "MVVM 의 흐름",
  "width": 620,
  "height": 418,
  "nodes": [
    {
      "id": "user",
      "title": "사용자",
      "x": 250,
      "y": 8,
      "w": 120,
      "h": 40,
      "aside": true
    },
    {
      "id": "v",
      "title": "View",
      "sub": "버튼과 라벨",
      "x": 235,
      "y": 120,
      "w": 150,
      "h": 54
    },
    {
      "id": "p",
      "title": "ViewModel",
      "sub": "화면용 상태",
      "x": 235,
      "y": 232,
      "w": 150,
      "h": 54
    },
    {
      "id": "m",
      "title": "Model",
      "sub": "데이터와 규칙",
      "x": 235,
      "y": 344,
      "w": 150,
      "h": 54
    }
  ],
  "edges": [
    {
      "from": "user",
      "to": "v",
      "label": "누른다",
      "bend": 62
    },
    {
      "from": "v",
      "to": "p",
      "label": "명령",
      "bend": 62
    },
    {
      "from": "p",
      "to": "m",
      "label": "TakeDamage(10)",
      "bend": 62
    },
    {
      "from": "m",
      "to": "p",
      "label": "OnChanged(90)",
      "bend": 62
    },
    {
      "from": "p",
      "to": "v",
      "label": "바인딩이 갱신한다",
      "bend": 62,
      "auto": true
    },
    {
      "from": "v",
      "to": "user",
      "label": "보인다",
      "bend": 62
    }
  ],
  "steps": [
    {
      "edge": 4,
      "why": "먼저 한 번만 선언해 둡니다. \"이 라벨은 저 속성을 따라간다.\" 청록색 선은 우리가 쓰는 코드가 아닙니다."
    },
    {
      "edge": 0,
      "why": "사용자가 버튼을 누릅니다."
    },
    {
      "edge": 1,
      "why": "View 가 ViewModel 의 명령을 부릅니다."
    },
    {
      "edge": 2,
      "why": "ViewModel 이 Model 에게 시킵니다. 여기까지는 MVP 와 똑같습니다."
    },
    {
      "edge": 3,
      "why": "Model 이 알리고 ViewModel 의 속성이 바뀝니다."
    },
    {
      "edge": 4,
      "why": "여기서 갈립니다. MVP 라면 Presenter 가 SetText 를 부를 자리인데, 아무도 안 부릅니다. 프레임워크가 바인딩을 보고 알아서 갱신합니다."
    },
    {
      "edge": 5,
      "why": "화면이 갱신됩니다. 사라진 것은 Render 메서드와 구독 해제 두 줄입니다."
    }
  ],
  "note": "청록색 선이 프레임워크 몫입니다. 그 선을 제공하는 프레임워크가 없으면 MVVM 은 성립하지 않습니다."
}
</script>
</x-wiring>

**그런데 이건 우리가 선택할 수 있는 게 아닙니다.**
잇는 일을 대신해 줄 무언가가 있어야 성립합니다.
유니티에서 그 자리를 채우는 것이 둘입니다.

### 하나. UI Toolkit 의 런타임 바인딩

**Unity 6 부터** UI Toolkit 이 런타임 데이터 바인딩을 지원합니다.
위 코드가 그것이고, 한 번 선언해 두면 **갱신을 부르는 곳이 아예 없습니다.**
uGUI 에는 이런 시스템이 없습니다.

### 둘. R3

라이브러리로 그 자리를 메우는 길도 있습니다.
R3 는 UniRx 의 후속이고 **Unity 2021.3 부터** 됩니다.
UniRx 시절부터 실무에서 오래 쓰였고, uGUI 와 UI Toolkit 양쪽에 붙습니다.

```csharp
// 값이 바뀌면 라벨이 따라갑니다.
model.Hp
    .Select(hp => $"{hp} / 100")
    .SubscribeToText(label)
    .AddTo(this);
```

`ReactiveProperty` 가 값과 알림을 한 덩어리로 들고, `SubscribeToText` 가 라벨에 꽂고,
`AddTo` 가 이 구독을 오브젝트 수명에 묶습니다.
MVP 의 `Render` 메서드와 `OnEnable` / `OnDisable` 짝이 이 한 줄로 접힙니다.

**다만 이 줄은 우리가 씁니다.** 그게 둘의 차이입니다.

| | UI Toolkit 바인딩 | R3 |
| --- | --- | --- |
| 잇는 줄을 누가 쓰나 | 아무도. 선언만 합니다 | 우리가. 대신 한 줄입니다 |
| 구독 해제 | 시스템이 | `AddTo` 가 |
| 붙는 UI | UI Toolkit | uGUI 도 UI Toolkit 도 |
| 필요한 것 | Unity 6 | Unity 2021.3 과 라이브러리 하나 |

둘 다 접착제를 걷어내는 쪽으로 갑니다. 다른 것은 그 한 줄이 **내 코드로 남느냐**뿐입니다.

> **그래서 고르는 기준은 어느 UI 를 쓰느냐가 아닙니다.**
> R3 는 Unity 2021.3 부터 uGUI 에서도 UI Toolkit 에서도 됩니다.
> 실제로 갈리는 자리는 **팀이 리액티브 코드를 읽을 수 있느냐**입니다.

`Select`, `Where`, `CombineLatest` 가 눈에 익은 팀에서는 저 한 줄이 제일 짧은 답입니다.
그렇지 않은 팀에서는 같은 한 줄이 벽이 됩니다. 읽는 사람이 못 읽는 코드는 짧아도 짧은 게 아닙니다.

## 무엇을 쓸까

**여기에 정답은 없습니다.** 팀이 무엇을 읽을 수 있는지, 화면이 몇 개인지,
그 화면이 앞으로 얼마나 복잡해질지에 따라 답이 바뀝니다.
남이 정리해 둔 표를 그대로 가져다 쓸 수 있는 종류의 문제가 아닙니다.

그걸 전제로, 제가 지금 고른다면 이렇습니다.

**UI Toolkit 을 쓰는 프로젝트면 MVVM 입니다.** 바인딩이 이미 있는데 접착제를
손으로 쓸 이유가 없습니다.

**uGUI 면 기본은 MVP 입니다.** 라이브러리 없이 되고, 들어온 사람이 바로 읽습니다.
다만 조건이 둘 겹치면 바인딩 쪽으로 갑니다. **팀의 R3 이해도가 높고,
Presenter 가 불어나는 게 눈에 보일 때**입니다. 표시 요소가 계속 늘어나는 화면이라면
`Render` 가 길어지는 속도를 한 번 재 보고 정하면 됩니다.

나머지 둘은 어느 쪽을 골랐든 같이 적용됩니다.

**화면 하나에 라벨 하나뿐이면 나누지 않습니다.** 클래스 셋과 파일 셋이 드는데
얻는 게 없습니다. 패턴을 안 쓰는 것도 선택지입니다.

**서버가 데이터를 관리하면 클라이언트의 Model 은 사본입니다.** 온라인 게임에서
진짜 Model 은 서버에 있고, 클라이언트가 들고 있는 것은 **서버 상태를 받아 둔 캐시**입니다.
그렇게 보기 시작하면 "왜 클라이언트가 계산한 값을 믿으면 안 되는가" 가 자연스럽게 정리됩니다.

## 밟기 쉬운 것 셋

**1. Model 에 화면 개념이 새어 들어갑니다.** `Color` 나 `Sprite` 가 Model 에 들어가는 순간
그 Model 은 UI 없이 시험할 수 없게 됩니다. Model 은 숫자와 규칙까지입니다.

**2. 구독 해제를 빠뜨립니다.** MVP 의 가장 흔한 사고입니다.
`OnEnable` 과 `OnDisable` 을 대칭으로 두는 것을 규칙으로 못 박습니다.

**3. Presenter 를 누가 만드는지 정하지 않습니다.** Model 을 누가 만들어 어떻게 넣어 주는지가
실제 프로젝트에서 가장 먼저 부딪히는 문제인데, 패턴 설명에는 잘 안 나옵니다.
`new` 를 부르는 자리를 한곳으로 모으든, 바깥에서 넣어 주든, **패턴을 고르기 전에 이걸 먼저 정해야 합니다.**

## 정리

- 셋 다 데이터와 화면을 세 조각으로 나눈다. 나누는 방법으로는 구분되지 않는다.
- 갈리는 것은 **나눈 뒤에 남는 참조의 방향**이다.
- MVC 는 Model 과 View 가 직접 붙어 있어 셋이 얽힌다. 유니티에서 잘 안 쓴다.
- MVP 는 가운데가 유일한 통로다. Model 은 View 를, View 는 Model 을 모른다.
- MVP 의 부담은 접착제다. 값을 표현으로 바꾸는 코드와 구독 해제를 사람이 쓴다.
- MVVM 은 그 접착제를 넘겨 버린다. 데이터 바인딩 하나가 전부다.
- 넘길 곳이 둘이다. UI Toolkit 의 런타임 바인딩(Unity 6)과 R3 같은 라이브러리(Unity 2021.3)다.
- 라이브러리 쪽은 잇는 한 줄이 내 코드로 남는다. 대신 어느 UI 에서든 된다.
- **정답은 없다.** 팀이 무엇을 읽는지, 화면이 얼마나 복잡해질지가 정한다.
  UI Toolkit 이면 MVVM, uGUI 면 기본은 MVP, 팀이 R3 를 읽고 Presenter 가 불어나면 바인딩 쪽이다.
- Model 에 색이나 스프라이트를 넣지 않는다. 넣는 순간 UI 없이 못 시험한다.

uGUI 를 쓰는 프로젝트가 아직 훨씬 많습니다. 그쪽에서는 MVP 가 현실적인 기본값입니다.