본문 바로가기

.NET/WPF

[WPF] MVVM 디자인 패턴 (1)

MVVM 디자인 패턴에 대해 알아보자.

WPF & .NET Framework로 개발한다는 가정하에 설명하려고 한다.

 

MVVM은 Model-View-ViewModel의 약자로 애플리케이션을 구성하는 요소를 모델, 뷰, 뷰 모델의 세 가지 계층으로 분리하여 각 계층 간의 결합도와 상호 의존성을 낮춤으로 분리 개발 및 유지보수에 용이하도록 하기 위한 디자인 패턴이다.

 

각 계층의 역할에 대한 어려운 설명은 인터넷에 검색하면 많이 나오니 참고하자.

이 글에서는 어려운 용어는 집어치우고 "그래서 각 계층을 도대체 어떻게 나누라는 건데?" 에 대해 생각해보려고 한다.

 

MVVM 디자인 패턴으로 개발을 할 때에 딱 1가지만 기억하자. 

 

뷰는 뷰 모델을 알지만 뷰 모델은 뷰를 알지 못하고, 뷰 모델은 모델을 알지만 모델은 뷰 모델을 알지 못한다.

 

여기서 '안다', '알지 못한다'는 참조가 일어나냐 일어나지 않느냐로 봐도 무방하다.

 

뷰(View)

뷰는 말 그대로 사용자에게 보여지는 영역이다. XAML(보통 재믈이라고 읽음) 코드로 디자인이 이뤄진다.

  • UI를 디자인한다.
  • 데이터를 보여주거나 데이터를 입력받을 컨트롤을 속성과 바인딩(Binding)한다.
  • 상호작용이 필요한 컨트롤의 이벤트를 등록한다.

뷰에는 코드비하인드라고 불리는 xaml.cs 파일이 함께 딸려있다. 코드비하인드는 주로 아래와 같은 목적으로 사용한다.

  • DataContext에 뷰 모델 개체를 생성하여 지정한다.
  • 뷰 모델에서의 처리가 필요하지 않은 이벤트들에 대한 처리 코드를 작성한다.
  • 뷰 모델로 명령을 전달한다.

 

뷰 모델(View Model)

뷰 모델은 MVVM 디자인 패턴에서 뷰와 모델을 이어주는 가장 중요한 계층임과 동시에 가장 헷갈리는 계층이다.

  • 뷰와 바인딩(Binding)될 속성(Property)들을 정의한다.
  • 모델의 데이터를 뷰가 필요로 하는 형식에 맞게 변형하여 각 속성에 보관한다.
  • 뷰에서 요청한 명령(Command)에 대한 처리 코드를 작성한다.

모델(Model)

모델은 순수 데이터를 담기 위한 목적의 클래스들이다. 모델은 모델에 담을 데이터를 어디에서 가져오는지 모르며, 직접 가져오지도 않는다. 데이터를 처리하거나 변환하지도 않는다.

모델이 담아야 할 데이터의 타입만 알고, 그 타입의 멤버들만 가질 뿐이다.

모델은 데이터와 데이터를 처리하는 비즈니스 로직을 포함하는 계층이다.

 

여기까지 뷰, 뷰 모델, 모델에 대해 간략히 정리해봤다. 그러나 여전히 이해하기는 쉽지 않다.

이해를 돕기 위해 적절한 예시 상황을 통해 각 계층이 하는 일을 나눠보도록 하자.

 

상황 : 비디오 대여 관리 프로그램에서 회원 ID로 해당 회원의 정보를 검색하여 조회하는 동작을 한다.

 

  • 회원 ID 입력 칸, 검색 버튼, 검색 결과를 표시할 각 항목들에 대한 컨트롤(이름, 연락처, 주소 등)이 존재한다. (MainWindowView.xmal)
  • 검색 버튼은 뷰 모델에 Command를 전달하도록 되어있다. (XAML코드에서 처리한다. 코드비하인드에서 처리해도 되지만 보통 뷰 모델로의 명령 전달은 XAML 코드에서 이뤄지는 게 좋다.) (MainWindowView.xmal, line 23)
  • 사용자는 회원 ID를 입력(Binding에 의해 입력된 값이 뷰 모델의 SearchID 속성에 지정된다.)하고 조회 버튼을 클릭(뷰 모델의 Command를 CanExecute() 해본 뒤 Execute()한다.)한다. (MainWindowView.xmal, line22~23)

뷰 모델

  1. 뷰 모델의 CanExecute()는 조회 명령을 처리할 수 있기 때문에 true를 반환한다. 이어서 조회 명령이 Execute()된다. (MainWindowViewModel.cs, line 131)
  2. DB에 질의를 날리기 위한 클래스를 통해 SearchID를 사용하여 회원 조회 쿼리를 날리고, 그 결과는 모델 객체로 받는다.(MainWindowViewModel.cs, line 145)

모델

  1. 그저 쿼리의 결과를 각 변수에 담는다. 각 변수의 타입은 검색될 데이터들의 각 컬럼 타입들과 동일하거나 유사하도록 미리 선언된 것이다. (이름을 저장할 변수는 string, 나이를 저장할 변수는 int, 가입일을 저장할 변수는 DateTime 등..) (MemberInfoModel.cs)

다시 뷰 모델

  1. 모델의 데이터들을 각 속성에 적절하게 변환하여 저장한다. (MainWindowViewModel.cs, line 149)
  2. 각 속성에 데이터가 저장되는 시점에서 속성의 값이 바뀌었기 때문에 OnPropertyChanged 이벤트가 발생한다. (자동으로 발생하는 것은 아니고 발생하도록 짠다.)

마지막으로 뷰

  1. 뷰 모델에서 속성의 값이 바뀜을 감지하고 바뀐 값들이 바인딩된 컨트롤에 표시된다.

 

위 동작을 수행하는 프로그램을 실제 WPF에서 MVVM 패턴으로 구현한 코드를 첨부하였다.

WPF나 MVVM이 낯선 사람은 코드를 완전히 이해하기는 어려울 것이다. 각 시나리오에 어떤 파일의 몇 번째 라인을 확인하면 좋을지 함께 기록하였으니 참고하자.

 

WpfMvvm.zip
0.06MB

 

코드에 대한 분석을 다음 포스트에서 진행해보겠다.

(MVVM의 핵심 요소인 Binding, ICommand, INotifyPropertyChanged 등을 위주로 설명하며, 디자인에 대한 설명은 따로 진행하지 않는다.)

 


MVVM 디자인 패턴에서 가장 혼동스러운 부분이 ViewModel과 Model의 역할 경계를 정하는 일이라고 생각한다.

 

이 글에서 설명한 것 처럼 Model은 데이터의 저장만을 제공한다고 설명하는 정보가 있는가 하면, 데이터에 대한 유효성 검사나 간단한 가공등을 제공할 수 있다고 설명하는 정보도 있다. Model에 대해 정의한 내용 중 가장 널리 통용되는 것은 'Model은 데이터와 비즈니스 로직만을 포함한다.'는 내용일 것인데, 비즈니스 로직은 데이터의 생성, 표시, 저장 및 변경하는 부분을 일컫는 용어이기에 이 설명에 따르면 Model이 데이터 처리를 수행하는 코드를 포함하는 것은 합당하다고 볼 수 있다.

 

애초에 디자인 패턴이라는 것이 다소 추상적인 개념이기도 하고 어디까지나 개발의 방향성을 제시할 뿐이지 세부적인 구현 방법 자체를 명시하는 것이 아니라는 점을 염두에 두고 정보를 접할 필요가 있음을 명심하자.

가장 중요한 것은 디자인 패턴이 제시하는 개발의 방향성을 따라감으로 인해 얻을 수 있는 궁극적인 효과를 깨뜨리지 않는 선에서 적당히 융통성 있게 적용하는 것이 아닐까 싶다.