티스토리 뷰
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가 항상 정답은 아닙니다. 요구사항과 일정에 따라 적정 레벨을 선택하되, 한번 정한 규칙은 일관되게 유지하는 것이 중요합니다.
결론
코딩이 시작되기 전, 설계 단계에서 다음 질문에 답할 수 있어야 합니다.
- View가 ViewModel 타입을 알아야 하는 부분이 있는가? → 있으면 설계 재검토.
- ViewModel이 View를 직접 다루어야 하는 부분이 있는가? → 있으면 Binding이나 AttachedProperty로 우회 가능한지 먼저 검토.
- Model에 View/ViewModel 관련 로직이 들어갔는가? → 들어갔으면 ViewModel로 이동.
모든 결정은 코딩 전에 끝나야 하며, 단일 기준은 "타입 의존성 배제"입니다.
참고
- Total
- Today
- Yesterday
- 싱가폴
- 도커컨테이너
- 2017 티스토리 결산
- 도커각티슈케이스
- 도커티슈케이스
- Sh
- docker container whale
- docker container
- vim
- docker container tissue box
- docker container tissue
- 개발자
- docker container case
- Linux
- 도커티슈박스
- 도커각티슈박스
- 이직
- shellscript
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |