1. MVVM 패턴이란?
MVVM은 Model-View-ViewModel의 약자로, 디자인 패턴 중 하나입니다. 주로 WPF 및 Xamarin과 같은 XAML 기반의 UI 프레임워크에서 사용되며, 사용자 인터페이스를 개발하는 데 유용합니다. 이 패턴은 UI를 비즈니스 로직과 분리하여 관리하기 위해 만들어졌습니다.
2. 구성 요소
1) Model
애플리케이션의 비즈니스 로직과 데이터를 처리하는 부분입니다. 데이터의 유효성 검사, 저장 및 검색 기능 등을 담당합니다.
2) View
사용자에게 표시되는 UI 부분으로, XAML 파일이 주로 담당합니다. 데이터 바인딩을 통해 ViewModel의 상태를 보여주고, 사용자 입력을 ViewModel에 전달합니다.
3) ViewModel
View와 Model 사이에서 중개자 역할을 합니다. View가 필요로 하는 데이터를 제공하고, 사용자 입력을 처리하여 Model에 전달합니다. 또한 View와 완전히 분리된 형태로 UI의 상태와 동작을 관리합니다.
MVVM을 구성하는 3가지 요소의 역할과 책임을 이해하기 위해서는 먼저 이들 사이의 관계를 알아야 합니다. 뷰는 뷰 모델을 알지만, 뷰 모델은 뷰를 알지 못합니다. 뷰 모델은 모델을 알지만, 모델은 뷰모델을 알지 못합니다.
이런 구조를 통해서 뷰 모델과 모델이 뷰로부터 독립적인 형태를 만들어서 UI로부터 비즈니스 로직과 프레젠테이션 로직을 분리라는 목적을 이룰 수 있게 됩니다.
3. 동작 방식

1) 데이터 바인딩
View와 ViewModel 사이에 양방향 데이터 바인딩을 구현하여, ViewModel의 상태가 변하면 이를 View에 반영하고, 사용자가 View에서 입력하면 이를 ViewModel에 전달합니다.
2) Command 패턴
사용자 입력을 처리하기 위해 ICommand 인터페이스를 통해 Command 패턴을 구현합니다. 버튼 클릭 등의 이벤트는 Command를 실행하여 ViewModel에서 해당 작업을 처리합니다.
Command는 UI에서 발생하는 이벤트와 실행 로직(메서드 또는 기능) 사이의 결합을 제거하고, 코드를 더 모듈화하고 유연하게 만들어줍니다.
Command를 사용하는 주요 이유 중 하나는 코드의 재사용성과 유지보수성을 높이는 데에 있습니다. 일반적으로, UI 요소(예: Button)에 직접적으로 클릭 이벤트 핸들러를 연결하는 대신 Command를 사용하여 해당 이벤트를 처리합니다.
3) 테스트 용이성
ViewModel은 비즈니스 로직을 포함하고 있으므로, 테스트하기가 용이합니다. ViewModel을 테스트하는 것은 UI 요소를 테스트하는 것보다 더 쉽고 간편합니다.
4. 장단점
1) 장점:
유연성과 유지보수성: 각 구성 요소가 분리되어 있으므로 코드를 이해하고 수정하기가 쉽습니다.
테스트 용이성: ViewModel은 비즈니스 로직을 담고 있어 테스트하기가 편리합니다.
재사용성: ViewModel은 여러 View에서 재사용될 수 있습니다.
2) 단점:
학습 곡선: 처음에는 익숙해지기가 어려울 수 있습니다.
복잡성: 작은 프로젝트나 단순한 UI에서는 과도하게 복잡할 수 있습니다.
MVVM은 UI와 비즈니스 로직을 분리하여 유지보수성을 높이고, 테스트 용이성을 증가시키는 등의 장점을 가지고 있어 많은 개발자들에게 선호되는 패턴 중 하나입니다.
5. 예제
MVVM 패턴 예제입니다.

구성은 이렇게 되어있습니다.
models폴더의 PersonModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfMVVM.Models
{
public class PersonModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string name)
{
//매개변수에 해당하는 필드의 변화가 있을때마다 알려주는 메서드
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(name));
}
private string name;
public string Name
{
get { return name; }
set { name = value;
OnPropertyChanged("Name");
}
}
private int age;
public int Age
{
get { return age; }
set
{
age = value;
OnPropertyChanged("Age");
}
}
}
}
views폴더의 PersonView.xaml
<Window x:Class="WpfMVVM.Views.PersonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfMVVM.Views"
mc:Ignorable="d"
Title="PersonView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--뷰모델의 리스트 연결부분-->
<ListView x:Name="lv" ItemsSource="{Binding PersonList}" Grid.Row="0">
<ListView.View>
<GridView>
<!--PersonList의 필드 연결부분-->
<GridViewColumn Header="이름" DisplayMemberBinding="{Binding Name}" Width="150"/>
<GridViewColumn Header="나이" DisplayMemberBinding="{Binding Age}" Width="150"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Center">
<!--SelectedItem은 선택된 정보가 나타남-->
<TextBox x:Name="tbox1" Width="150" Text="{Binding ElementName=lv, Path=SelectedItem.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Width="150" Text="{Binding ElementName=lv, Path=SelectedItem.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<!--순서1 버튼에서는 바인딩 되어있는 PersonViewModel의 PersonCommand를 실행함-->
<Button Command="{Binding PersonCommand}"
CommandParameter="{Binding ElementName=tbox1,Path=Text}">버튼</Button>
</StackPanel>
</Grid>
</Window>
views폴더의 PersonView.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WpfMVVM.ViewModels;
namespace WpfMVVM.Views
{
/// <summary>
/// PersonView.xaml에 대한 상호 작용 논리
/// </summary>
public partial class PersonView : Window
{
public PersonView()
{
InitializeComponent();
DataContext = new PersonViewModel();
}
}
}
viewModels폴더의 PersonViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using WpfMVVM.commands;
using WpfMVVM.Models;
namespace WpfMVVM.ViewModels
{
class PersonViewModel
{
public PersonCommand PersonCommand { get; set; }
//아래 메서드를 통해 view와 바인딩 됨
public List<PersonModel> PersonList { get; set; }
public PersonViewModel()
{
PersonList = new List<PersonModel>
{
new PersonModel{Name="홍길동",Age=100 },
new PersonModel{Name="임꺽정",Age=90 },
new PersonModel{Name="유관순",Age=70},
new PersonModel{Name="이순신",Age=60},
};
//순서 2 Msg메서드를 생성자에서 넘겨줌 ->PersonCommand로 이동
//매개변수로는 메서드만 들어감
PersonCommand = new PersonCommand(Msg,CheckMsg);
}
//txt는 뷰에서 넘어옴
protected void Msg(string txt)
{
//순서 7 파라미터를 받아 메서드 실행
MessageBox.Show(txt);
}
public bool CheckMsg(string txt)
{
if(txt.Length > 0)
{
return true;
}
else
{
return false;
}
}
}
}
commands 폴더의 PersonCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfMVVM.commands
{
public class PersonCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
Action<string> exe;
Predicate<string> canexe;
//순서3 넘어온 msg메서드를 받음
public PersonCommand(Action<string> msg,Predicate<string> checkMsg)
{
//순서 4 msg 메서드를 Action deligate인 exe에 주입
exe = msg;
canexe = checkMsg;
}
//Execute를 실행할지 정하는 메서드, true일때는 실행 false일때는 실행안함
public bool CanExecute(object? parameter)
{
//순서 5 true인지 false인지 확인
return canexe.Invoke(parameter as string);
}
public void Execute(object? parameter)
{
//순서 6 true일 경우 PersonViewModel의 Msg메서드로 파라미터 넘기고 실행함
exe.Invoke(parameter as string);
}
}
}
※ 참고 시작 url 조정
App.xaml
<Application x:Class="WpfMVVM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMVVM"
StartupUri="/Views/PersonView.xaml">
<!--시작 URL, 절대경로로 입력-->
<Application.Resources>
</Application.Resources>
</Application>
'CS(Computer Science) 이론 > 디자인패턴' 카테고리의 다른 글
[디자인 패턴] MVC 패턴 (0) | 2024.04.25 |
---|