C#으로 본 MVVM 패턴 정리 및 활용

MVVM 패턴은 Model과 View Model, View로 이루어진 패턴입니다.

Model은 개념을 나타내는 Entity 입니다. 데이터를 나타내는 기본 단위가 됩니다.

View는 유저가 보는 그래픽 컨트롤들의 집합입니다. WPF의 윈도우나 Web 페이지가 될 수 있습니다.

VIewModel은 View와 Model를 연결하는 요소입니다.  MVVM패턴에서 가장 중요한 역할을 하는 요소입니다.

ViewModel은 UI 로직, 커맨드, 이벤트, 모델에 대한 참조가 포함이 되어 있습니다.

그리고 데이터바인딩을 통해 View에 표시된 데이터를 갱신하게 되는데, View가 가지고 있는 Observer가 뷰모델을 보고 있으면서 데이터가 변경되면 스스로 UI를 갱신하게 됩니다. UI를 갱신할 때, ViewModel은 INotifyPropertyChanged를 상속받으며, ProperryChanged 이벤트를 발생 시켜야합니다.

SoC(Separation of Concerns) 즉, 관심사의 분리는 레이어로 구성된 어플리케이션을 만드는 컨셉을 가지고 있습니다. SoC는 레이어 별로 기능을 분리함으로써 위험을 줄이고 복잡한 설계를 좀 더 단순하게 만드는 방법을 제공합니다.

이것을 소개하는 이유는 MVVM 패턴이 만들어진 이유이기 때문입니다. MVVM 패턴은 관심사를 분리 시켜 기능을 분산시키고, 집중되는 것을 막습니다. 이를 통해 위험도를 낮추고 설계를 편리하게 하는데 도움을 줍니다.

MVVM 코드
MVVM를 사용하는 이유

 

MVVM을 사용하는 WPF에서 구현해야하는 인터페이스

  1. INotifyPropertyChange : property 값이 변경될 때 통지시스템을 구현하며, 제대로 동작하는 바인딩엔진을 만들기 위해 ViewModel에서 구현이 필요하다.
  2. ICommand : Xaml 컨트롤과 바인딩 되며, 액션과 이벤트를 실행할 수 있도록 해준다.
  3. Data Template : ViewModel이나 ViewModel의 상태를 어떻게 렌더링할지 정의한다. 특징으론 런타임에 렌더링되기 떄문에 동적으로 생성된다.

MVVM의 장점과 단점

장점 

1. View는 VIewModel을 관찰하고 있기 때문에 좀 더 분리된 UI를 쉽게 만들 수 있다.

2. 프리젠테이션 로직을 전혀 수정하지 않더라도 뷰를 대체할 수 있다.

3. 다른 패턴에 비해 View에 직접 기능을 연결하기 때문에 비슷한 기능을 통합할 수 있다.

단점

  1. 생소한 구조의 프로그램을 만들 때는 도입하지 않는 것이 좋다.
  2. MVVM에서 사용되는 기술에 대해서 러닝 커브가 존재한다.
  3. 작은 프로젝트에서는 유용하지 않다.

이유는 MVVM을 어플리케이션을 더욱 모듈화를 해주는데 이러한 모듈화가 개발을 복잡하게 할 수 있기 때문이다. 작은 프로젝트에서는 굳이 모듈화를 할 필요가 없는 경우가 많다.

MVVM 모델
MVVM 모델

위 그림을 보면 MVVM을 이해하는데 도움이 될 것입니다.

View는 View Model에게 데이터와 Command를 요청하고 ViewModel은 그 요청을 Model에게 전달합니다.

그 과정에서 요청을 한 View와 ViewModel은 자신이 보낸 요청이 제대로 전달 됐다고 notification을 받아 갱신을 하게 됩니다.

 

MVVM 패턴을 사용하기 전 알면 좋은 정보

MVVM 패턴도 하나의 디자인 패턴이라고 볼 수 있다. 이 말은 자주 사용되는 기본적인 디자인 패턴을 알고 MVVM을 이해하는 것이 좋다는 의미입니다.

생성, 구조, 행위라는 3개의 카테고리 속해 있는 자주 사용되는 디자인 패턴은 알아두는 것이 좋습니다.

 

MVC vs MVP vs MVVM

MVC 패턴은 모델, 뷰, 컨트롤러로 구성된 패턴입니다.

이 패턴의 장점을 보면 다른 뷰에서 같은 모델을 표시할 수 있고, 모델에 영향없이 뷰를 표현하는 방법을 바꿀 수 있다는 점입니다.

이렇게 분리되어 있기 때문에 테스트하기도 용이합니다.

TDD 접근에 적합하게 해준다.

 

단점으로는 의존성을 최대한 줄였고, 이벤트를 통해 변화를 주고 받기 때문에 업데이트 부분에 대해서 상당한 리소스를 사용해야 합니다.

그래서 WPF 같이 UI가 관찰자 기능을 가지기 위해서는 사용하기 좋지 않습니다.  대신 UI가 들어가지 않는 어플리케이션에 경우 SoC를 잘 반영할 수 있기 때문에 사용하면 좋습니다.

추가적으로 Controller에 기능이 집중되어 Controller가 비대해 질 수 있습니다. 이 경우 종속성이 강해지면서 테스트가 용이해 진다는 장점이 사라질 수 있습니다.

그리고 View와 강하게 종속되는 경우도 발생할 수 있습니다. 결국 View를 변경하면 Controller를 변경해야 할 경우도 생기기 때문에 제대로 분리해야 합니다.

 

MVP

MVP 패턴은 모델과 뷰, 프리젠터로 구성되어 있습니다. MVC는 완벽한 분리였지만 MVP는 다른 접근방식을 적용하여 연결이 되어 있습니다.

프리젠터는 UI로직을 제어합니다. 인터페이스를 통해 모델과 뷰를 전부 알고 있으며 뷰에서 변경 통지를 주면 모델을 업데이트합니다. 뷰의 메서드를 호출하여 바인딩 엔진을 사용한 효과를 냅니다.

MVP의 장점은 MVC의 단점인 업데이트 부분의 리소스 사용을 해결이 되었다는 점이다. 그래서 UI를 갱신하거나 사용자에게 데이터 갱신 요청을 받을 때 처리하기 편리하다.

단점으로는 WPF와 같은 기술에서 제공하는 바인딩 엔진을 사용하지 않았고, XAML 코드를 완전히 분리할 수 없습니다.

그리고 모든 프리젠테이션 로직과 바인딩 프로세스가 프리젠터에 의존적이라는 것이 단점입니다.

SoC가 제대로 반영되지 않습니다.

코드를 작성할 때 각 View별로 프리젠터를 작성하는 것이 아니라면 프리젠터가 비대해져서 MVC의 컨트롤러와 비슷하게 유지보수가 어려워 질 수 있습니다.

 

.Net 에서 제공하는 MVVM 패턴의 중요 요소들 

결국 MVVM에서 가장 중요한 역활을 하는 것은 ViewModel입니다. ViewModel에서 얼마나 제공하느냐에 따라 View와 Model이 영향을 받기 때문입니다.

뷰모델은 4가지 주요한 요구 조건이 있습니다.

1. 데이터는 뷰에서 노출되어야 한다.

2. 뷰에서 사용 가능한 커맨드들을 제공한다. 이 커맨드는 ICommand를 통해 만든다.

3. INotifyPropertyChanged 인터페이스를 구현한다.

4. IDataErrorInfo 인터페이스를 구현한다.

물론 4가지 구성요소 전부를 만족시키는 것은 아닙니다. UI 변경을 알리지 않을 수도 있고, 데이터 유효성 검사가 필요 없을 수도 있으며 커맨드가 필요 없는 경우도 있습니다. 다만 필요할 떄는 위에 요구 조건에 맞춰서 구현해야 합니다.

INotifyPropertyChanged 인터페이스는 Observable 객체로 인터페이스를 상속하여 사용하는 것이 좋습니다. 그리고 호출을 통해 UI 변경을 알림으로써 동적으로 UI를 변경 할 수 있도록 기능을 제공합니다.

public class Demo : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public string PhoneNumber
    {
        get
        {
            return this.phoneNumberValue;
        }
        set
        {
            if (value != this.phoneNumberValue)
            {
                this.phoneNumberValue = value;
                NotifyPropertyChanged();
            }
        }
    }
}

IDataErrorInfo 인터페이스는 View에 바인딩되는 객체에 구체적인 오류 정보를 제공하기 위해 사용되는 인터페이스 입니다. IDataErrorInfo는 Error와 Item이라는 두 개의 속성을 제공합니다. Error는 유효성 검사 에러를 나타낼 때 사용합니다. Item은 view에 있는 항목이 변경되거나 유효성 검사가 필요할 때 호출 됩니다.

public partial class Movie : IDataErrorInfo
{
     partial void OnTitleChanging(string value) 
     { 
     	if (value.Trim().Length == 0) 
        	_errors.Add("Title", "Title is required."); 
     }
     
     public string this[string columnName] 
     { 	
     	get 
     	{ 
        	if (_errors.ContainsKey(columnName)) 
            	return _errors[columnName]; 
            
            return string.Empty; 
        } 
     }
}

 

지금은 IDataErrorInfo보다 DataAnnotations를 활용하여 데이터 유효성 검사를 하곤 합니다.

using System.ComponentModel.DataAnnotations;

[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstName { get; set; }

TextBox Text="{Binding LastName , UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" /> 

위와 같은 방식으로 사용합니다.

MVVM과 IOC

IoC(Inversion of control)은 제어역행이란 뜻으로 어플리케이션 제어를 역전시키는 프로그래밍 기술입니다. 즉, 호출자는 응답만 하면되며, 객체를 사용하는 것에 대한 결정을 할 필요가 없다.  이렇게 하면 호출자는 어플리케이션 흐름의 제어에 대한 권한을 가지지 않게 됩니다.

이러한 IoC를 통해 MVVM에서 뷰와 뷰모델이 서로 의존성을 가지게 하는 것을 방지할 수 있다. 흐름을 살펴보자.

  1. IoC 컨테이너를 활성화
  2. 라이프 사이클을 정의하는 정책을 사용 후 공통 서비스와 유틸리티를 등록한다.
  3. 내비게이션 서비스와 유틸리티에 대한 상대 의존관계와 뷰모델을 등록한다.
  4. 해당하는 뷰모델의 최종 인젝션과 뷰를 등록한다.
// 뷰 모델 등록
var vm = iocContainer.Resolve();
vm.InitializeData();

// 뷰 등록
var view = iocContainer.Resolve();

// 데이터 바인딩
view.DataContext = vm;

// INavigator 서비스를 통해 뷰 오픈
var service = iocContainer.Resolve();
servocie.ShowView(view);

코드를 보면 iocContainer에 뷰와 뷰모델, INavigator 등의 서비스를 넣어서 관리하며, 필요할 때마다 호출하여 사용하는 것을 볼 수 있다.

결론 및 정리

더욱 자세한 내용은 MVVM패턴을 이용한 엔터프라이즈 어플리케이션 책을 참고하면 된다. 필자도 많이 참고한 책이다.

또한 안드로이드에서 MVVM패턴을 사용하기 때문에 찾아보는 것을 추천한다. 패턴은 상황에 맞춰 잘 사용했을 때 좋은 효과를 발휘하므로 장,단점을 꼭 숙지하고 사용해야 한다.

 

읽어보면 좋은 글

좋은 개발자가 되고 싶은 모든 분들에게

Leave a Comment