티스토리 뷰

카테고리 없음

MVVM 패턴 핵심 요약

KyeongRok Kim 2026. 4. 29. 10:44

MVVM 패턴 핵심 요약

한 줄 요약

View와 ViewModel은 타입 수준에서 서로를 참조하지 않아야 한다.

이것이 MVVM의 전부입니다. 나머지는 모두 이 원칙을 어떻게 지킬 것인가에 대한 부연 설명입니다.


왜 그래야 하는가

ViewModel은 View의 추상화입니다. 추상화의 목적은 View가 독립적으로 존재할 수 있도록 하는 것입니다.

  • View가 ViewModel 타입을 알면 → ViewModel 변경이 View에 영향을 줍니다.
  • ViewModel이 View 타입을 알면 → View 변경이 ViewModel에 영향을 줍니다.

서로의 타입을 모르고, 속성 간 Binding으로만 연결되어야 양쪽 모두 독립적으로 유지됩니다.


안티패턴

View가 ViewModel을 직접 생성하거나 타입을 명시하는 경우

<!-- 안 됩니다 -->
<Window.DataContext>
    <viewmodels:AppViewModel />
</Window.DataContext>
// 안 됩니다
public class AppViewModel
{
    public AppViewModel(AppView view) { ... }
}

Model이 ViewModel/View 동작에 관여하는 경우

// 안 됩니다 - Model이 ViewModel 로직을 가짐
public class UserSet
{
    public string Category
    {
        set
        {
            _category = Filter(value);          // View 입력 처리는 ViewModel 책임
            ViewModel.LastUpdateTime = ...;     // Model이 ViewModel 참조
        }
    }
}

올바른 구조

// ViewModel - View를 모릅니다
public class AppViewModel
{
    public AppViewModel() { }
    public string Name { get; set; }
}
<!-- View - ViewModel 타입을 명시하지 않습니다 -->
<Window>
    <TextBlock Text="{Binding Name}"/>
</Window>

DataContext외부에서 주입합니다. WPF에서는 DataTemplate이 ViewModel 타입과 View를 매핑하는 역할을 합니다.


"그럼 이런 경우엔 어떻게 하나" 질문이 나오면

이미 설계가 잘못된 신호입니다. 다음 순서로 고민합니다.

1순위: Binding으로 해결

값이 바뀌면 View가 알아서 반응합니다. ViewModel은 값만 바꾸고 그 이후는 신경 쓰지 않습니다 (fire & forget).

2순위: AttachedProperty / Behavior

Binding만으로 부족할 때 사용합니다. 예: ListBox.ScrollIntoView() 같은 명령형 동작.

// AttachedProperty로 해결한 ScrollIntoView 예시
public class ListBoxExtension
{
    public static readonly DependencyProperty BringIntoViewItemProperty =
        DependencyProperty.RegisterAttached(
            "BringIntoViewItem", typeof(object), typeof(ListBoxExtension),
            new PropertyMetadata(OnChanged));

    static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is ListBox listBox)
            listBox.ScrollIntoView(e.NewValue);
    }
    // Get/Set 생략
}
<ListBox local:ListBoxExtension.BringIntoViewItem="{Binding SelectedSource}"
         ItemsSource="{Binding Source}"
         SelectedItem="{Binding SelectedSource}"/>

이 경우 View와 ViewModel 사이에 어떤 타입 의존성도 없습니다.

3순위 (마지노선): 인터페이스를 통한 우회 접근

IView 같은 인터페이스로 ViewModel이 View를 참조합니다. 권장하지는 않지만 어쩔 수 없을 때의 마지노선입니다. 이 이하로는 내려가지 않습니다.

절대 금지: View와 ViewModel이 서로의 구체 타입을 직접 참조하는 것

여기까지 내려갈 거면 MVVM을 쓰는 의미가 없습니다.


Model 설계 원칙

  • DB Entity나 DTO를 Model로 직접 쓰지 않습니다. 별도 타입으로 정형화합니다.
  • Model은 데이터 보관용입니다. View 입력 처리 로직은 ViewModel에 둡니다.
  • Model이 immutable이라면 View와 직접 Binding해도 무방합니다. 그렇지 않다면 ViewModel을 거쳐서 노출합니다.
  • 컬렉션의 개별 아이템은 상위 입장에서는 Model, ItemTemplate 입장에서는 ViewModel로 동시에 작동합니다. 정상입니다.

구현 레벨

레벨 상태 특징
1 패턴 무지 그냥 막 만듦
2 View → ViewModel 직접 참조 분리 의식은 있으나 code-behind에 로직이 몰림
3 인터페이스 우회 참조 학습 수준은 높지만 타입 의존성은 잔존
4 완전 분리 IoC + Binding + AttachedProperty/Behavior로 구현

레벨 4가 항상 정답은 아닙니다. 요구사항과 일정에 따라 적정 레벨을 선택하되, 한번 정한 규칙은 일관되게 유지하는 것이 중요합니다.


결론

코딩이 시작되기 전, 설계 단계에서 다음 질문에 답할 수 있어야 합니다.

  1. View가 ViewModel 타입을 알아야 하는 부분이 있는가? → 있으면 설계 재검토.
  2. ViewModel이 View를 직접 다루어야 하는 부분이 있는가? → 있으면 Binding이나 AttachedProperty로 우회 가능한지 먼저 검토.
  3. Model에 View/ViewModel 관련 로직이 들어갔는가? → 들어갔으면 ViewModel로 이동.

모든 결정은 코딩 전에 끝나야 하며, 단일 기준은 "타입 의존성 배제"입니다.

참고

https://blog.naver.com/vactorman/222615603748 를 정리했습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/05   »
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
글 보관함