2017년 새해가 밝아 왔습니다. 하지만, 아직까지도 MVVM Pattern 지역은 그렇게 밝게 빛나지 않고 있는 것 같습니다. 1월 14일에 부산에서 열리는 Microsoft UWP(Universal Windows Platform) App 개발 세미나에서 설명을 위해서 사용할 MVVM Pattern(이하 MP로 사용하기도 합니다)을 개발자를 위한 안내 포스트를 작성하려고 합니다.


0. 참고

The MVVM Pattern


Todo list Universal & UWP app


MVVM pattern 설명 동영상(오래전에 녹화한 내용입니다.)
http://youtu.be/f9aQkuoiPz4
http://youtu.be/uGxboAUwciI
http://youtu.be/2lQQiBEjbtU


Using MVVM Pattern




1. MVVM Pattern의 시작

MVVM은 WPF (Windows Presentation Foundation) 및 Silverlight의 기능을 활용하여 사용자 인터페이스의 이벤트 중심 프로그래밍을 간소화하기 위해 Microsoft 아키텍처 Ken Cooper 및 Ted Peters가 개발했습니다. 그리고, Microsoft의 WPF 및 Silverlight 아키텍트 중 한 명인 John Gossman은 2005 년 자신의 블로그에서 MVVM을 발표했으며, 벌써 12년이 지났습니다. 하지만, 아직까지 국내에서는 MP에 대한 인지율이 낮고, 제대로 알지 못한 상태에서 사용하여 프로젝트가 산으로 가는 경우가 많은 것 같습니다.



2. kaki's MVVM Pattern

처음 MVVM Pattern을 접한 것이 2010년 10월 교육센터에서 였으며, 그 때부터 지금까지, WPF, Silverlight, Windows app 개발 프로젝트를 진행하였고, 2016년에는 S사에서 Flex로 개발된 솔루션을 WPF로 컨버전하는 프로젝트에서 아키텍처를 담당하였습니다. Prism 5.0을 기반으로 MVVM Pattern, Telerik UI Control을 이용해서 프레임웍을 구축하고, Custom Control, 공통 모듈 개발을 진행했습니다.



3. MVVM Pattern에 대한 Q&A

Q. MVVM Pattern을 꼭 사용해야 하나?

A. MP는 WinForm이나 ASP.Net 개발에서는 사용할 수 없으며, 오직, WPF, Silverlight, UWP app 개발에서만 사용이 가능합니다. 만약, WPF로 개발하는데 MP를 사용하지 않는다고 하면, 그냥 WinForm 프로젝트로 변경해서 진행하는 것이 더 효과 적이라고 생각합니다. 결과적으로는 WPF, Silverlight, UWP app 실무 프로젝트 개발은 MP를 사용해야 한다고 보시면 됩니다.


Q. 코드 비하인드(MainPage.xaml.cs)에 코딩을 하지 못하나?

A. MP의 기본 방향은 View와 ViewModel의 분리입니다. 그렇기 때문에 될 수 있으면, 코드 비하인드에 직접 코딩을 하지는 않습니다. 하지만, Control에 내장되어있는 Method를 호출해야하는 경우라든지, View에 붙어서 해야하는 작업인 경우에는 코드 비하인드에 직접 코딩을 하는 것이 더 효율적입니다. 이렇게 작업을 하는 경우에는 메모리 누수가 발생하지 않도록 추가적으로 코딩을 해주는 것이 중요합니다.


Q. 개발 속도가 느리다.

A. MVVM Pattern의 목적은 개발 속도 향상이 아닌 유지보수의 비용 감소에 있습니다. 그렇기 때문에 WinForm 개발 속도와 비교를 할 수는 없습니다. 정말 빠르게 개발하고 완료를 해야하는 프로젝트라면, WinForm 프로젝트로 진행하는 것을 권장 합니다. 다만, 개발 완료 후 다년간 성능 개선과 유지보수를 진행한다고 했을 때는 제대로 만들어진 MP 프로젝트가 WinForm 프로젝트보다는 적은 리소스가 들어간다고 할 수 있습니다. 특히! Unit Test를 도입을 한다면, 유지보수에 들어가는 비용은 더욱더 줄어들 것입니다.


Q. 뷰 혹은 컨트롤을 뷰모델에 바인딩해서 사용해도 되나요?

A. MP의 기본은 뷰와 뷰모델을 서로 직접 연결하지 않는 것에 있습니다. 그러므로, 뷰 혹은 컨트롤을 직접 바인딩해서 뷰모델에서 프로퍼티를 변경하거나, 조작하는 방식은 사용하지 말아야 합니다. 만약 이렇게 사용하게 되면 메모리 누수의 직접적인 원인이 되어, 차후에 문제가 발생합니다.



4. MVVM Pattern의 핵심 개념

1) Model : 데이터를 처리하는 기본 단위로 데이터 클래스를 이야기 합니다.

2) View : 사용자가 보고, 입출력하는 화면으로 xaml 파일을 이야기 합니다

3) ViewModel : View의 추상화 클래스로, 비지니스 로직이 구현되어 있습니다.





5. UWP 앱을 예를 들어서 설명을 하도록 하겠습니다.


Template10을 이용한 UWP Blank app 프로젝트를 만든 후 버튼을 하나 추가했습니다.

버튼을 클릭하면 Hello UWP World라는 글씨로 변경됩니다.






1) MVVM Pattern을 사용하지 않는 일반적인 event-driven 방식을 이용해서 변경되는 과정을 보겠습니다.


화면에 Hello World라는 글씨가 나오는 부분과 버튼에 해당하는 부분의 MainPage.xaml 코드를 보겠습니다.


        <RelativePanel EntranceNavigationTransitionInfo.IsTargetElement="True"
                       RelativePanel.AlignBottomWithPanel="True"
                       RelativePanel.AlignLeftWithPanel="True"
                       RelativePanel.AlignRightWithPanel="True"
                       RelativePanel.Below="pageHeader">

            <!--  content  -->
            <TextBlock x:Name="textBlock"
                       Margin="16,12,0,0"
                       Text="Hello World" />
            <Button Content="Change Text" RelativePanel.Below="textBlock" Margin="10"
                    Click="Button_Click"/>
        </RelativePanel>


. TextBlock 컨트롤의 Text 프로퍼티에 "Hello World"라는 글씨가 입력되어 있습니다. 앱을 실행하면 바로 이 내용이 화면에 출력됩니다.

. 그 컨트롤 아래 Button 컨트롤의 Content라는 프로퍼티에 "Change Text"라는 글씨를 넣어 놓았습니다. 또한, 버튼을 클릭하면 Button_Click이라는 코드비하인드에 있는 메소드를 실행하게 됩니다.



MainPage.xaml.cs 코드 비하인드에 있는 Button_Click이라는 메소드의 내용을 살펴보겠습니다.


        private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            textBlock.Text = "Hello UWP World";
        }


textBlock.Text라는 곳에 "Hello UWP World"라는 문자열을 입력하도록 되어있습니다. 즉, textBlock이라는 컨트롤의 Text 프로퍼티의 값을 직접 변경함으로 화면에 출력되는 내용이 변경됩니다.


위의 예제에서 Button 컨트롤의 Click 이벤트를 이용해서 어떤 작업을 하도록 지시를 했기 때문에 이런 처리 방식을 event-driven이라고 이야기 합니다. 또한, 화면을 구성하는 MainPage.xaml 파일에 Click="Button_Click"이라고 입력된 내용과 MainPage.xaml.cs에 Button_Click 메소드는 서로 직접 연결이 되어 있기 때문에 tightly coupled 연결이라고 이야기 합니다.


* event-driven 정리

이 방식에서 필요한 것은 딱 2가지 입니다. 화면과 코드 비하인드..그만큼 알아야 할 내용도 추가적으로 공부할 내용도 없는 매우 간단한 구조입니다.


그런데 이렇게 간단한 구조이지만, 프로젝트의 규모가 커지고 투입 인원이 늘어나고, 유지보수 기간이 늘어 날 수록 여러가지 문제가 발생하게 됩니다. 메모리 누수, 패턴이 없기 때문에 개발자마다 코딩 방법이나 표현 방법이 달라 유지보수에 리소스가 많이 소비됨, 성능문제 등이 가장 대표적인 문제 입니다.



2) MVVM Pattern으로 변경해 보겠습니다.


MainPage.xaml를 보겠습니다.


        <RelativePanel EntranceNavigationTransitionInfo.IsTargetElement="True"
                       RelativePanel.AlignBottomWithPanel="True"
                       RelativePanel.AlignLeftWithPanel="True"
                       RelativePanel.AlignRightWithPanel="True"
                       RelativePanel.Below="pageHeader">

            <!--  content  -->
            <TextBlock x:Name="textBlock"
                       Margin="16,12,0,0"
                       Text="{Binding HelloText}" />
           
            <Button Content="Change Text" RelativePanel.Below="textBlock" Margin="10"
                    Command="{Binding TextChangeCommand}"/>
        </RelativePanel>


. TextBlock 컨트롤에 Text 프로퍼티에 "{Binding HelloText}"라고 되어 있습니다. MP에서 가장 중요한 Binding이라는 기술을 이용해서 ViewModel에 있는 HelloText 프로퍼티를 연결해 놓은 것입니다.

. Button 컨트롤에 Command라는 프로퍼티에 "{Binding TextChangeCommand}"라고 되어 있습니다. Button은 기본적으로 Command라는 프로퍼티를 가지고 있으며, Click 이벤트가 발생하면 Command 프로퍼티와 연결되어 있는 ViewModel에 TextChangeCommand라는 커맨드를 실행합니다.


위에서 사용한 Binding이란 개념과 Command에 대해서 잠시 살펴 보겠습니다.


* Binding은 Data Binding을 이야기 한 것으로, ViewModel에 특정 프로퍼티와 컨트롤의 특정 프로퍼티를 서로 연결해서 ViewModel의 프로퍼티의 값이 변경되면, 자동으로 컨트롤의 프로퍼티 값도 변경되는 형태를 이야기 합니다.

여기서 프로퍼티의 값이 변경되었을 때 바인딩 대상에서 알리는 방법은 INotifyPropertyChanged 인터페이스를 이용합니다.

* UWP 앱에서는 Binding 문으로 사용하는 duck type 바인딩과 x:Bind 문으로 사용 non duck type 바인딩을 사용할 수 있으며, 더 자세한 사항은 데이터 바인딩 개요 페이지를 참고하시면 됩니다.



* Command는 화면에서 발생하는 사용자의 Interaction을 ViewModel에 전달하는 중간자 역할을 합니다. 명령을 실행 시키는데 필요한 Parameter를 전달하기 위해서 CommandParameter라는 프로퍼티도 사용할 수 있습니다.



MainPageViewModel.cs 코드를 보겠습니다.


    public class MainPageViewModel : ViewModelBase
    {
        private string _helloText;

        public MainPageViewModel()
        {
            Init();
        }

        private void Init()
        {
            HelloText = "Hello World";

            TextChangeCommand = new DelegateCommand(() =>
            {
                HelloText = "Hello UWP World";
            });
        }

        public string HelloText
        {
            get { return _helloText; }
            set { Set(ref _helloText ,value); }
        }

        public ICommand TextChangeCommand { get; set; }
    }


처음에 우리가 보았던 화면은 Hello World라는 글씨가 출력되어 있었고, 버튼을 눌러서 글씨를 변경하는 내용이 였습니다. 이 중 비지니스 로직만 분리를 하면,


- Hello World라는 글씨가 처음부터 출력되어 있어야 함

- 버튼을 누르면 Hello UWP World라는 글씨로 변경한다.


입니다. 이 2가지의 기능만을 수행하는 별도의 클래스를 ViewModel이라고 부르게 되는 것입니다.


뷰모델의 코드를 보면 HelloText라는 프로퍼티는 뷰모델이 생성이 되면서 Hello World라는 문자열 값을 가지게 되고, TextChangeCommand가 실행되면, Hello UWP World라는 문자열로 변경됩니다. 이때 Set(ref _helloText, value) 문장에서 기존 문자열 값과 새로 들어온 값이 서로 다르다면, NotifyPropertyChanged 이벤트가 발생됩니다. 그러면, 뷰에 HelloText 프로퍼티와 Binding되어있던, textBlcok의 Text 프로퍼티의 값이 변경되면서, 바뀐 문자열이 표시 됩니다.



3) 그렇다면.. 여기서 궁금한 것이 있습니다. 어떻게 뷰모델과 뷰가 서로 연결되는 것일까요?


연결 방법은 몇가지가 있지만, 가장 근본적으로는 뷰모델이 인스턴스가 되어서 뷰의 DataContext에 입력이 되면 됩니다. 일반적으로 뷰의 DataContext는 뷰모델이 들어가 있다고 생각하면 될 것 같습니다.


위의 예제에서 연결한 방법은 다음과 같습니다.


<Page x:Class="T10MVVMSample.Views.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:controls="using:Template10.Controls"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:T10MVVMSample.Views"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:vm="using:T10MVVMSample.ViewModels"
      mc:Ignorable="d">


    <Page.DataContext>
        <vm:MainPageViewModel/>
    </Page.DataContext>



디자인 타임이나, 런타임시에 뷰가 인스턴스가 되면, 뷰모델도 인스턴스를 시켜서 DataContext 프로퍼티와 연결해 줍니다.


이 방법 이외에도, 디자인타임 뷰모델과 런타임 뷰모델을 서로 다르게 인스턴스 시켜서 사용하는 방법도 있습니다. 주로 Prism을 이용하거나 Container을 이용해서 객체를 생성하는 경우는 다른 방법을 사용해야 하겠습니다.


* MVVM Pattern 정리


이 예제에서는 MainPage.xaml, MainPageViewModel.cs 2개의 파일을 사용하였으며, Data Binding이라는 기술과, Command를 이용한 사용자 인터렉션 처리를 했습니다. 이렇게 MP를 이용해서 개발을 진행하기 위해서는 추가적으로 개념을 정리하고, 배워야 할 내용들이 많습니다. 아마도 그런 이유 때문에 어렵게 느끼는 것이 아닌가 생각합니다. 하지만, 그렇다고 포기하면 프로젝트를 하는 것은 요원한 일이 됩니다.



6. MVVM Pattern을 사용하기 위해서 기본적인 개념을 이해 하고 있어야 하는 내용들


1) Blend for Visual Studio 2015

: Visual Studio 2015는 코딩용 툴입니다. 디자인 작업은 Blend For Visual Studio 2015를 이용하는 것이 좋습니다.

.2) Binding 기술

: MP에서 가장 중요한 기술로 UWP에서는 OneTime, OneWay, TwoWay 방식을 이용할 수 있습니다.

3) IValueConverter

: Model의 value를 View에 출력할 때 형태를 변경해서 출력하는 것을 도와줍니다.

: Value conversion with IValueConverter 내용을 참고하세요

4) ICommand

: View에서 발생하는 사용자 Interaction을 ViewModel에 전달하는 용도로 사용합니다.

: WPF - ICommand 동작 방식 포스트를 참고하세요

5) Behavior, Action

: Control에 이벤트를 직접적으로 처리를 해야하는 경우에 만들어 사용합니다.

: Creating custom behaviors 내용을 참고하세요

6) Template

: Model의 value를 양식에 맞추어서 ListView, GridView 등의 컨트롤에 연속적으로 출력하기 위해 사용합니다.

: Styling and Templating 내용을 참고하세요.

7) Style

: 컨트롤의 디자인을 변경하거나, 컨트롤에 기능을 추가하는 용도로 만들거나, 수정해서 사용합니다.

: Styling and Templating 내용을 참고하세요.

8) Selector

: DataTemplateSelector, StyleSelector 등 Model의 value값이나 조건에 따라서 다르게 적용해야 하는 경우에 사용합니다.

: DataTemplateSelector class 셀렉터에 대한 정리된 포스트가 잘 없네요..


7. MVVM Pattern이 제대로 적용된 프로젝트가 조금 더 활성되었으면..


위에서 언급했던 S사에서도 사내에서 .Net 개발을 하지 않았기 때문에, WPF도 처음, Prism도 처음, MVVM Pattern도 처음 사용하는 분이 대부분이였습니다. 하지만, 기본 프레임웍을 구축하고 셈플 화면을 만드는 과정에서 꼼꼼하게 주석을 추가하고, 개발 방법에 대한 문서를 정리하고, 약간의 교육만을 진행했는데도 불구하고, 기본 화면 만드는 작업 일정은 무리 없이 진행이 되었습니다.


프로젝트를 진행하는 주체와 프로젝트를 리딩하는 PM, PL이 조금만 MVVM Pattern에 대한 이해와 관심을 가져주고, 프로젝트에 투입되는 인력도 모른다고, 귀찮다고, 이게 내스타일인데~를 외치지 말고 표준 형태의 Pattern을 배우고, 모르면 물어보면서 확인하면서 진행한다면 더욱더 성공하지 않을까 생각됩니다.




블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바