MVVM (Model – View – ViewModel)
Model, View, ViewModel로 구성된 애플리케이션 구조 패턴
2005년 Microsoft의 John Gossman이 자신의 블로그에 WPF와 SilverLight의 아키텍쳐중 하나로 공개
MVVM 패턴의 구성 요소
-
Model
-
애플리케이션의 기본 구성요소이다. 데이터가 표시되는 방식에서 의존성을 제거하는 것이 이 계층의 목적이다.
-
View
-
사용자 인터페이스로 XAML 페이지로 구성된다. Visual Layout을 정의하는 모든 컨트롤 및 애니메이션을 포함한다.
-
ViewModel
-
Model로 부터 데이터를 검색 및 처리하고 화면에 표현될수 있도록 조작한다.
-
MVC나 MVP 패턴과의 차이점은 View와 의존성이 없는 단순한 클래스로 구현된다는 것이다.
MVVM 패턴의 목표
애플리케이션의 구조를 정의하여 개발자를 돕기 위한 것. 코드와 사용자 인터페이스 사이의 강한 결속력을 없애고 비지니스 로직과 데이터 표현을 구분하는 것이다.
WPF에서는 데이터 바인딩, 커맨드, 이벤트등을 통해 사용자와 상호작용하고, 서로간의 의존 관계를 명확하게 분리하는 것을 목표로 한다.
MVVM 패턴을 사용하는 이유
기존 애플리케이션의 패턴과 구조의 문제
- 기존 코드에 새로운 기능을 추가하거나 버그를 수정하는 것이 어렵다.
- 유니테스트를 수행하기가 복잡하다.
- 사용자 인터페이스와 비지니스로직의 긴밀한 관계로 파트간 독립적으로 개발하기가 어렵다.
- 기존의 패턴들은 메소드의 집합이거나 API를 사용하는 방법뿐, 애플리케이션의 구조를 정의하는 방법이 아니다.
MVVM 패턴을 적용하여 해결
- 세가지 다른 계층으로 코드를 분할하고 각 계층 사이의 의존성이 없기 때문에 팀에서 작업하는 경우 애플리케이션을 유지보수하고 확장하는 것이 용이하다.
- 컨트롤이 이벤트 핸들러에 연결되는 방식에서는 유니테스트를 위해 이벤트 시뮬레이션(버튼 클릭등)을 직접 해야하는 문제가 있는데,
MVVM 패턴은 ViewModel을 분리하여 사용자 인터페이스와 의존성을 끊고, ViewModel을 테스트할 수 있는 코드를 포함할 수 있다. - 사용자 인터페이스와 비지니스 로직간의 긴밀한 연결이 끊어지기 때문에 모듈 구현시에 모든 내용을 알 필요가 없다. ViewModel을 쉽게 교체하고 실제 사용되는 데이터를 가짜로 만들어낼 수도 있으며, 이는 각 파트별로 개발하는데 도움이 된다.
XAML을 이용한 MVVM 패턴의 구현
1) 바인딩
XAML 컨트롤 또는 코드에 선언된 컨트롤 속성간에 통신 채널을 생성한다.
public List<Order> Orders { get; set; }
위 컬렉션을 ListView 또는 GridView와 연결하려면 기존에는 코드에서 수동으로 할당해야 했었다.
MyList.ItemsSource = Orders;
XAML 바인딩을 사용하면 아래와 같이 선언하여 의존성을 끊을 수 있다.
<ListView ItemsSource="{Binding Path=Orders}" />
2) DataContext
바인딩을 사용하여 뷰 모델에 포함된 속성을 모두 연결할 수 있다.
XAML에서 제공하는 DataContext를 이용하여 ViewModel 클래스 전체를 바인딩 할 수 있다.
DataContext를 사용하면 모든 public 속성을 XAML에서 사용할 수 있으며, 속성뿐만 아니라 컨트롤 자체도 제어할 수 있다.
MVVM 패턴의 구현 핵심은 이 계층에 의존하는데, ViewModel로 만든 클래스가 해당 페이지에 DataContext로 정의되는 것이다.
<Page x:Class="Sample.MainPage"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding Source={StaticResource MainViewModel}}"
mc:Ignorable="d">
</Page>
3) INotifyPropertyChanged
속성간에 바인딩을 사용하여 연결하면 실제 데이터가 변경되어도 화면에는 반영되지 않는다. 따라서 이를 ViewModel에서 제어하기 위해 INotifyPropertyChanged를 사용하여 속성 값이 변경을 UI에 알리는 부분을 구현한다. 모든 ViewModel이 INotifyPropertyChanged를 구현해야하기 때문에 일반적으로 INotifyPropertyChanged를 구현한 ViewModelBase 클래스를 미리 정의해서 사용한다.
(MVVM Light에서는 INotifyPropertyChanged를 구현해놓은 ViewModelBase 클래스를 제공한다)
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
OnPropertyChanged();
}
}
}
4) ICommand
사용자와의 상호작용 처리를 위해서 이벤트 핸들러를 이용하여 코드를 작성하는데, 이는 View에 강한 의존성을 가지게 되고 Code-behind에만 선언가능하다. 이 상호작용 처리를 위해 Command를 사용하는데, ICommand 인터페이스를 사용하여 의존성을 제거할 수 있다. Command 를 정의하면 XAML 기능을 이용하여 상호작용 처리가 가능한 모든 컨트롤에 연결할 수 있다.
(MVVM Light에서는 ICommand를 구현해놓은 RelayCommand를 제공한다)
// 전통적으로 사용되는 이벤트 핸들러 등록 방식
<Button Content="Click me" Click="OnButtonClicked" />
private void OnButtonClicked(object sender, RoutedEventArgs e)
{
//do something
}
// ICommand 사용
public class ClickCommand : ICommand
{
public bool CanExecute(object parameter)
{
}
public void Execute(object parameter)
{
}
public event EventHandler CanExecuteChanged;
}
// XAML 바인딩
<Button Content="Click me" Command="{Binding Path=ClickCommand}
MVVM 구현을 위한 Toolkit과 Framework
- MVVM Light (http://www.mvvmlight.net)
- Caliburn Micro (http://caliburnmicro.com)
- Prism (http://github.com/PrismLibrary/Prism)
- Unity (https://mvvmandunity.codeplex.com)