이 문서는 https://github.com/EpicGames/UnrealTournament 의 How to get started (Windows) 항목에 대한 번역입니다. 해당 github 에 접근하는 것에 실패했다면, [ GITHUB 계정 연동 ] 문서를 참조하시기 바랍니다.

 


 

현재 언리얼 토너먼트 프로젝트는 언리얼 엔진 4.12 프리뷰 버전에 기반합니다( 역주 : 나중에는 버전이 더 올라갈 수 있습니다 ). 편의를 위해서 이 엔진 소스는 이제 우리 저장소에 포함됩니다. 윈도우즈에서의 사용을 위해서는 비주얼 스튜디오 2015 가 필요합니다.

 

언리얼 토너먼트 프로젝트 다운로드하기:

    • 이 페이지에 있는 Download ZIP 파일을 클릭해서 프로젝트를 다운로드하십시오.
    • 여러분의 컴퓨터에 있는 폴더에서 파일의 압축을 해제하십시오( 역주 : zip 파일의 크기는 작지만, 설치과정을 마치고 에디터를 실행하게 되면 최종적으로는 60 GB 이상의 용량이 필요합니다 ).
    • 여러분이 생성했던 디렉토리 내의 Setup.bat 파일을 실행하십시오. 이는 GitHub 에 저장되어 있지 않은 필요한 바이너리들을 다운로드하게 됩니다.
    • Engine\Extras\Redist\en-us 폴더에 있는 UE4PrereqSetup_x64 를 실행하십시오. 이는 누락되어 있을 수도 있는 다른 종속성 파일들을 설치할 것입니다( 역주 : Setup.bat 파일을 실행하는 과정에서 이것이 같이 실행되었다면, 굳이 설치할 필요가 없습니다 ).

 

언리얼 토너먼트 프로젝트 컴파일하기:

    • Engine\Source\Programs\UnrealSwarm 에 있는 UnrealSwarm.sln 을 열기 위해서 비주얼 스튜디오를 사용하십시오. 솔루션 구성( Solution Configuration ) 드롭다운 메뉴에서 Development 모드를 선택하십시오.
    • Agent 프로젝트에서 마우스 오른쪽을 클릭해서 속성( Properties )를 선택하십시오. 서명( Singing ) 탭으로 이동하여, "Sign the ClickOnce manifests" 에 대한 선택을 해제하십시오( 역주 : 소스를 받으면 기본값으로 해제되어 있습니다 ).
    • 마우스 오른쪽을 클릭해서 Agent 프로젝트를 빌드하십시오. 빌드가 끝나면 UnrealSwarm 솔루션을 닫아도 됩니다.
    • 프로젝트 압축을 해제한 디렉토리에서 GenerateProjectFiles.bat 를 실행하십시오. 이는 UE4.sln 을 생성할 것입니다.
    • UE4.sln 을 비주얼 스튜디오로 열어서, 솔루션 구성을 Development Editor 로 변경하십시오.
    • Programs 폴더에 있는 ShaderCompileWroker 와 UnrealLightMmass 를 빌드하십시오.
    • UnrealTournament 프로젝트를 빌드하십시오. UE4Editor.exe 가 Engine\Bindaries\Win64 에 생성될 것입니다.
    • 명령줄에서 "UE4Editor.exe UnrealTournament" 를 실행하거나, 비주얼 스튜디오에서 UnrealTournament 프로젝트에서 마우스 오른쪽을 클릭해서 디버그로 실행하십시오.
가끔 GenerateProjectFiles.bat 을 호출하면 Social 모듈 파일이 없다고 나오는데, 그건 zip 파일이 잘못 올라 가 있는 것입니다. 다른 버전의 파일을 받으시든지 [ 이 페이지 ] 의 Common issues 항목을 참조하시든지 하십시오. 


Unreal Tournament( UT ) 란 Epic Games 와 Digital Extremes 에서 공동개발한 1인칭 슈팅( First Person Shooting ) 게임입니다. UE4 로 넘어 오면서는 그냥 Epic Games 에서만 개발하고 있는 것으로 보입니다. UT 사이트에 가 봤더니 다른 공동 개발사에 대한 언급은 없더군요.

 

어쨌든 UT 는 Counter Strike 와 같은 전통적인 FPS 게임과는 다르게 총을 쏘는 즉시 반응하는 것이 아니라 나가는 궤적을 볼 수 있으며 곡사도 가능합니다. 그리고 매우 다양한 패턴의 무기가 존재합니다. 이런 부분들이 우리나라 게이머들에게는 별로 취향에 맞지 않는지 우리나라에서는 인기가 없습니다.

 

그럼에도 불구하고 제가 UT 를 분석하려고 하는 이유는 다음과 같기 때문입니다.

    • 최신버전에 맞게 갱신된 소스가 공개되어 있습니다.
    • 매우 오랜 기간동안 개발해 와서 안정성이 상당히 높습니다.
    • Epic Games 에서 개발하고 있기 때문에 UE4 사용법에 대한 훌륭한 레퍼런스가 될 것으로 보입니다.

 

UT 분석을 통해서 다음과 같은 부분에 대한 이해도를 높이려고 합니다.

    • Character.
    • Animation.
    • Cosmetic.
    • Physics.
    • Networking.
    • Gameplay.
    • Slate.
    • Optimization.

 

어느 정도 깊이로 분석할지는 모르겠지만, 최선을 다해 볼 생각입니다.

못 그려도 그냥 올림. 하다 보면 늘겠지...



'ETC > Digital Painting' 카테고리의 다른 글

물고기2  (0) 2015.10.15
물고기  (0) 2015.10.14
인간 뼈대  (0) 2014.09.09
고양이 뼈대와 몸  (0) 2014.09.03
고양이 근육 연습  (0) 2014.08.31
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18

모티브


 

Unity 3D 는 다수 개의 게임 인스턴스를 용납하지 않기 때문에 시뮬레이션이 필요한 에디팅을 할 때는 정말 짜증나는 경우가 많다. 예를 들어 캐릭터 애니메이션 에디터를 만든다고 하자.

 

보통의 상용 엔진이라고 하면 새로운 씬이나 게임 인스턴스를 생성하도록 한다. 예를 들어 UE4 같은 경우에는 editor game instance, play-in-editor( pie ) game instance, 일반적인 game instance 들이 따로 존재할 수 있는 구조이다. 그리고 각 게임 인스턴스들의 입력, 씬 구성 등은 모두 다르다. 그러므로 에디터 메인 뷰를 띄워 놓은 상태에서 페르소나 애니메이션 툴을 따로 띄워 놓고 내부적으로 시뮬레이션을 수행할 수 있다.

 

그러나 유니티는 그런거 절대 없다. 시뮬레이션이 필요하다면 반드시 플레이 버튼을 눌러야 하며 씬도 한 번에 하나밖에 못 띄운다( 콜백을 받아서 편집 상태에서 시뮬레이션하는 방법이 있다고 하는데, 아직까지 해 본 적이 없어서 잘 모르겠다 ). 일단 게임 인스턴스라는 개념조차 없는 것 같으니 대충 비슷하게라도 처리할 수 있는 방법을 찾고 있었다. 그러던 차에 5.3.X 버전부터 멀티 씬 편집을 지원한다는 사실을 알게 되었다( 유니티도 UE 와 같이 스트리밍을 하고 싶었나보다 ).

 

유니티는 현재 시점에는 한글 도움말에는 이것에 대한 정보 자체를 공개하지 않고 있다. 하지만 영문 도움말에는 나와 있다. 이상하게 영문 도움말과 한글 도움말의 카테고리 구성조차 다르다. 뭐하자는 건지 잘 모르겠다. 어쨌든 이 멀티 씬 개념을 사용하면 적어도 오브젝트 관리라도 따로 할 수 있지 않을까라는 생각이 들어서 정확하게 어떤 개념인지 알아 보기로 했다. 목표는 아래와 같다.

 

1. 에디터에서 원하는 시점에 다중 씬을 구성할 수 있는지 확인.

2. 특정 씬의 개체를 획득할 수 있는지 확인.

3. 특정 씬을 편집할 수 없도록 잠글 수 있는지 확인.

4. 특정 윈도우를 띄웠을 때 원하는 뷰에서 특정씬만을 볼 수 있는지 확인.

5. 특정 씬만 플레이할 수 있는지 확인.

 

씬 구성


 

테스트 프로젝트를 하나 만들어서 "Main" 과 "Customizer" 라는 씬을 만들었다. Main 에는 카메라와 빈 게임 오브젝트가 들어 있고, Customizer 에는 도형들이 들어 가 있다. 서로 위치가 겹치지 않게 하기 위해서 ( 원하는 뷰가 나오는지 확인하기 위해서 ), 조금 다른 위치에 배치했다.

 

툴의 "Open Scene Additively" 메뉴를 사용해서 화면을 열면 다음과 같은 결과를 얻을 수 있다.

 

 

두 개의 씬이 올라 와 있고 서로 다른 계층 구조를 가지고 있는 것을 확인할 수 있다. 현재는 Main 씬이 활성화되어 있다.

 

새 윈도우에 씬 렌더링하기


 

위와 동일한 결과를 획득하면서 새로운 윈도우에 Customizer 씬을 렌더링하는 테스트를 수행했다. 이를 위해서 Customizer 라는 EditorWindow 를 제작했다. 주석을 열심히 달아 놓았으니 의미를 파악하기는 어렵지 않을 것이라 본다.

 

 

이렇게 했을 때 다음과 같은 결과를 얻을 수 있었다. Customizer 씬이 활성화되어 있는 것을 볼 수 있다. 씬이 활성화되어 있다는 의미는 다음과 같다. 새로운 게임 오브젝트들은 기본적으로 활성화된 씬에 포함된다.

 

 

 

즉 활성화 여부만으로는 편집을 막거나 렌더링을 막을 수 없다. 실제로 위의 상태에서 메인씬에 3D 텍스트를 추가해 보았다. 다음과 같은 결과를 얻을 수 있었다.

 

 

결론


 

위의 테스트를 통해서 다음과 같은 결론을 얻을 수 있었다.

 

1. 에디터에서 원하는 시점에 다중 씬을 구성할 수 있는지 확인 : 원하는 시점에 다중 씬을 구성할 수 있다.

 

2. 특정 씬의 개체를 획득할 수 있는지 확인 : 현재( 5.3.1f ) 특정 씬의 개체를 획득할 수 인터페이스는 없었다Workaround 가 있다GameObject 가 scene 이라는 멤버 필드를 가지고 있으므로, 모든 게임 오브젝트에 대해 루프를 돌면서 확인할 수는 있다. 5.3.2 버전 부터는 Scene.GetRootGameObjects() 라는 메서드를 지원한다고 하니 그것을 이용할 수도 있다.

 

3. 씬을 편집할 수 없도록 잠글 수 있는지 확인 : 잠글 수 없다. Workaround 가 있다. 만약 모달 윈도우를 띄울 수 있다면 비슷하게 할 수는 있을 것 같다. 그러면 추가/삭제가 활성화된 씬에서 이루어질 것이기 때문읻. 유니티에서 모달 시스템 구현을 하는 방법에 대한 아티클들이 있으니 참고해 볼 수 있을 것 같다.

 

4. 특정 윈도우를 띄웠을 때 원하는 뷰에서 특정씬만을 볼 수 있는지 확인 : 원하는 뷰에서 볼 수는 있지만 특정 씬만을 볼 수는 없다. Workaround 가 있다. 이쪽은 아직 자세하게 몰라서 그런데, tag 나 layer 같은 것을 써서 렌더링 요소를 filtering 할 수 있다고 들은 것 같다. 막 나갈라면 그냥 모든 요소 돌면서 Customizer 씬의 요소가 아니면 렌더링을 꺼 버리는 수가 있다. 물론 복구를 잘 해야 하지만...

 

5. 특정 씬만 플레이할 수 있는지 확인 : 이것은 4 번 항목과 유사한 문제라 할 수 있다.

 

결과적으로 볼 때, ( 설계를 매우 잘 해야 하겠지만 ) 여러 가지 꼼수들을 사용하면 다중 게임 인터페이스가 있는 것과 유사한 행동을 하도록 구성할 수는 있을 것 같다.

 

참고로 버그를 하나 발견했는데, Customizer 윈도우를 띄우고 나서 플레이를 누른다음에 창을 닫으면 Customizer 씬이 없어지지 않는다. 아마도 플레이 동작에서 씬에 대한 레퍼런스를 하나 들고 있는 것이 아닌가 싶다.

원문 : Choosing Between Class and Struct.

주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

 

모든 프레임워크 설계자가 설계를 함에 있어서 직면하게 되는 기본적인 결정중 하나는 클래스( 참조형, reference type )로서 타입을 설계할 것이냐 아니면 구조체( 값형, value type )으로서 타입을 설계할 것이냐입니다. 참조형과 값형의 행위의 차이를 잘 이해하는 것은 이 결정을 내리는 데 있어서 핵심적입니다.

 

우리가 고려해야 할 참조형과 값형의 첫 번째 차이는 다음과 같습니다. 참조형은 힙( heap )에 할당되며 garbage-collected 된다는 것입니다. 반면에 값형은 스택( stack )에 생성되거나 그것을 포함하는 타입 내부에 포함( inline in )됩니다. 그리고 값형은 스택이 언와인드( unwind )되거나 그것을 포함하는 타입이 해제될 때 같이 해제됩니다. 그러므로 값형의 할당과 해제는 일반적으로 참조형의 할당과 해제보다는 빠릅니다.

 

다음으로, 참조형 배열은 행 외( out-of-line )에서 할당됩니다. 이는 그 배열의 요소들이 단지 힙에 존재하는 참조형 인스턴스에 대한 참조일 뿐임을 의미합니다. 값형 배열은 인라인( inline )에 할당됩니다. 이는 그 배열의 요소들이 값형에 대한 실제 인스턴스임을 의미합니다. 그러므로 값 형 배열의 할당과 해제는 참조형 배열의 할당과 해제보다 훨씬 빠릅니다. 또한 대부분의 경우에 값형 배열은 더 나은 참조 국부성( locality of reference )을 지닙니다( 역주 : 메모리 주소가 가까워 연산 속도가 빠릅니다 ).

 

다음 차이는 메모리 사용( usage )과 관련이 있습니다. 값형은 참조형이나 값형이 구현하는 인터페이스 중의 하나로 캐스팅될 때 박싱( box )됩니다. 그것들은 다시 값형으로 캐스팅될 때 언박싱( unbox )됩니다. 박싱된 것은 힙에 할당된 오브젝트이며 garbage-collected 됩니다. 너무 많이 박싱과 언박싱을 되풀이하는 것은 힙, garbage collector 에 부정적인 영향을 줄 수 있으며, 극단적으로 응용프로그램의 성능을 저하시킵니다. 반면에 참조형들이 캐스팅될 때는 그러한 박싱이 발생하지 않습니다.

 

다음으로, 참조형 할당은 참조를 복사합니다. 반면에 값형 할당은 전체 값을 복사합니다. 그러므로 큰 참조형에 대한 할당은 큰 값형에 대한 할당보다 빠릅니다.

 

마지막으로, 참조형은 참조로 전달됩니다. 반면에 값형은 값으로 전달됩니다. 참조형 인스턴스에 대한 변경은 그 인스턴스를 가리키는 모든 참조들에 영향을 미칩니다. 값형 인스턴스들은 값으로 전달될 때 복사됩니다. 값형 인스턴스가 변경되면, 그것은 다른 복사본들에 영향을 미치지 않습니다. 복사는 사용자에 의해 명시적으로 이루어지는 것이 아니라 인자로 값이 넘어 가거나 값이 반환될 때 묵시적으로 이루어지기 때문에, 값형이 변경될 수 있도록 하는 것은 많은 사용자들을 혼란스럽게 만들 수 있습니다. 그러므로 값형은 불변성( immutable )을 가져야 합니다.

 

경험적으로 볼 때, 프레임워크 내에 존재하는 대부분의 타입들은 클래스여야 합니다. 그러나 값형의 특징상 구조체를 사용하는 것이 더 적절하게 만드는 특정 상황들이 존재합니다.

 

고려해야 하는 경우 만약 특정 타입의 인스턴스가 작고 보통 빨리 해제되는 경우나 보통 다른 오브젝트에 포함( embedded )되는 경우에는 클래스 대신 구조체를 정의하십시오.

 

피해야 하는 경우 타입이 다음과 같은 특징들을 모두 가지고 있지 않는 한, 구조체를 정의하지 마십시오( 역주 :  ).

    • 논리적으로 기본형( int, double 등 )과 유사하게 단일 값을 표현한다.
    • 인스턴스가 16 바이트 이하의 크기를 가진다.
    • 불변성( immutable )을 가진다.
    • 자주 박싱되지 않아야 할 것이다.

 

다른 경우에는, 타입을 클래스로서 정의해야 합니다.

'Programming > .NET' 카테고리의 다른 글

WPF IScrollInfo  (0) 2019.02.17
[ 8 ] Control Template  (0) 2012.10.24
[ 7 ] Style  (0) 2012.10.22
[ 6 ] WPF Content Model  (0) 2012.10.19
[ 5 ] DataTemplate  (0) 2012.10.17
[ 4 ] DataBinding  (0) 2012.10.14
[ 3 ] Dependency property.  (0) 2012.10.11
[ 2 ] XAML  (0) 2012.10.10
[ 1 ] WPF Architecture.  (0) 2012.10.08
[ 6 ] Proerty, indexer, attribute  (0) 2012.10.05

주의 : 공부하면서 정리한 것이라서 잘못된 내용이 있을 수 있습니다.

주의 : 이 글은 위키피디아의 모멘트를 주요 레퍼런스로 하여 작성되었습니다. 내용이 이상하면 원문을 참조하세요.

 

모티브


 

제가 물리 공부를 하다가 제일 먼저 접하게 된 헷갈리는 개념은 모멘트( moment )였습니다. [ 게임 개발자를 위한 물리 ] 같은 책에서는 질량관성모멘트( mass moment of interia )를 구하겠다면서, 뜬금없이 "질량계", "점질량", "1차 모멘트", "2차 모멘트" 등에 대한 별다른 설명없이, 그냥 계산식을 내 놓습니다. 고등학교 물리 정도는 대충 이해한 독자를 대상으로 한다고 서문에 써져 있기는 했지만, 저는 고등학교 졸업한지 20 년이 지난지라 이해할 수 없더군요.

 

하여간 검색해 보니, 대부분의 사람들은 추상적으로 계산 과정에 대해서만 다루고 모멘트 자체의 의미에 대해서는 설명하지 않더군요. 심지어는 모멘트와 모멘텀( momentum, 운동량 )에 대해서 헷갈리는 사람들도 있구요, "토크가 모멘트다"라고 하는 사람들도 있더군요. 그래서 이번 기회에 저와 같이 모멘트가 뭔지 모르는 사람들을 위해서 개념을 정리해 봐야겠다는 생각을 했습니다.

 

모멘트의 개념


 

위키피디아의 정의에 의하면 모멘트는 다음과 같습니다.

 

물리에서, 모멘트는 물리량( physical quantity )와 거리의 조합이다. 모멘트는 보통 기준( reference point )이나 축( axis )에 대해 정의된다; 모멘트는 물리량을 기준이나 축으로부터 일정 거리를 가진 것으로서 다룬다. 예를 들어 힘의 모멘트( moment of force )는 힘과 어떤 축으로부터의 거리의 곱인데, 이는 그 축에 대한 회전을 산출한다. 기본적으로 모멘트를 생성하기 위해서 어떤 물리량이라도 거리와 곱해질 수 있다; 보통 물리량은 힘( force ), 질량( mass ), electric charge distribution 이 사용된다.

 

- 출처 : Moment (Physics), Wikipedia.

 

간단하게 정리하자면, 모멘트는 어떤 기준에 대한 "거리 X 물리량" 을 의미합니다. 마치 내적( dot product )이나 외적( cross product )과 같은 개념이라고 생각하시면 됩니다. 일종의 함수라고 생각해야 한다고 할까나요? 내적과 외적은 수학적으로 보면 두 벡터를 곱하고 더하는 순서를 의미할 뿐입니다. 하지만 각각은 기하학적인 관점에서 보면 다른 의미를 가지고 있습니다. 마찬가지로 모멘트도 특정 상황에서 실질적인 의미를 가진다고 보시면 됩니다.

 

모멘트의 일반적인 표현식은 다음과 같습니다. 하지만 반드시 아래와 같은 형태인 것은 아닙니다. 왜냐하면 거리와 물리량의 조합이라는 것이 반드시 스칼라 곱을 의미하는 것은 아니기 때문입니다.

 

 

여기에서 Q 는 물리량( physical quantity )이고, r 은 거리입니다. 그런데 거리라는 것은 단순한 스칼라가 아니라 벡터일 수 있습니다. n 은 차수이구요. 만약 n = 1 이면 1차 모멘트라고 하고, n = 2 이면 2차 모멘트라고 합니다.

 

만약 물리량이 단일 점에 국한된 것이 아니라면 모멘트는 공간에서의 물리량들의 밀도에 대한 적분입니다.

 

 

여기에서 ρ 는 밀도입니다.

 

현재 시점에서는 내적이나 외적같이 그냥 공식으로서의 의미만 가지고 있습니다. 이제 이 모멘트가 어떤 식으로 사용되는지 한 가지 예를 들어 보도록 하겠습니다.

 

Torque


 

모멘트가 가장 단순한 형태로 사용되는 것은 토크( torque )입니다. 우리말로는 돌림힘이라고 합니다.

 

토크는 moment of force 를 의미하는데, 일반적으로 줄여서 그냥 moment 라고 하기도 한다고 합니다. 그래서 "토크가 모멘트다" 라는 말은 틀렸다고는 할 수 없지만, 엄밀한 정의를 생각하면 사용하지 않는 것이 낫지 않나라는 생각을 해 봅니다. 예를 들어 단위 벡터 A 와 어떤 벡터 B가 있을 때 A 와 B 를 내적하면, 그 결과는 B 를 A 에 사영한 길이가 나옵니다. 그렇다고 해서 내적의 정의를 "A 와 B 가 있을 때, B 를 A 에 사영한 길이이다" 라고 할 수는 없는 것이지 않겠습니까. 특수한 상황이고 자주 사용되는 상황이라고 해서 그냥 정의를 혼용해서 쓰는 것은 혼란을 가중시킨다고 생각합니다.

 

어쨌든 토크는 어떤 축( axis ), 받침점( fulcrum ), 중심( pivot )에 대해서 회전하게 하는 힘의 경향( tendency )를 의미합니다. 수학적으로 볼 때 토크는 힘이 적용되고 있는 점에 대한 위치 벡터와 회전을 유발하는 힘 벡터간의 외적입니다.

 

 

 

위의 식을 보면 거리 벡터와 힘 벡터의 조합임을 확인할 수 있습니다.

 

결론


 

모멘트는 거리와 물리량의 조합으로서, 다양한 물리량을 표현하기 위해서 사용됩니다. 그것의 활용예는 다양합니다. 위키피디아의 모멘트 항목의 See also 를 참조하시기 바랍니다.


추가 : [ http://contents.kocw.net/KOCW/document/2015/kumoh/ohchungseok/8.pdf ] 에 따르면 모멘트를 다음과 같이 정의하더군요.



UE4 용 VS 2015 프로젝트를 생성하면 아래와 같은 에러가 발생한 후에 build 가 제대로 안 되는 경우가 있습니다.

 

 

위에서 10.0.10586.0 버전의 ucrt 를 사용하는 것을 알 수 있습니다. 하지만 실제 해당 디렉토리에는 ucrt 가 생성되어 있지 않습니다.

 

그래서 Windows Software Development Kit - Windows 10.0.10586.0 을 설치해 봤습니다. 하지만 여전히 ucrt 는 생성되지 않습니다. Visual Studio 설치 프로세스를 통해 설치하면 위의 에러는 나지 않는데, 이상하게도 VS 2015 가 솔루션을 로드한 다음에 조금 있다가 크래쉬가 납니다. 결국 VS 를 다시 설치했습니다. 이건 순서가 어땠는지 헷갈리네요.

 

그래서 실제 VS 의 lib-path 는 어떻게 되어 있는지 확인해 봤습니다

 

 

그것은 10.0.10240.0 버전이었습니다. 그래서 [ [ 번역 ] Universal CRT 소개 ] 에서 언급한 것처럼 VC++ props 에 $(UniversalCRT_IncludePath) 등을 추가해 봤으나 아무런 소용이 없었습니다.

 

결국에는 UnrealBuildTool 에서 잘못된 lib-path 를 가지고 프로젝트를 생성한다는 결론을 내릴 수밖에 없었습니다. 아마도 10586 과 관련한 path 가 최신의 path 이기 때문에 그것을 지정하는 것이 아닌가 생각합니다. 이것을 수정하고 싶지만 귀찮아서 그냥 꽁수로 해결하기로 했습니다.

 

그래서 10240 버전이 아닌 path 들이 지정되는 것을 막기 위해서 다음과 같은 프로그램들을 제거했습니다.

 

 

이제 제대로 빌드가 됩니다. 회사에서는 잘 되었었는데, 집에서는 안 된 것은 WDK 를 최신버전으로 설치했느냐 그렇지 않느냐의 차이인 것 같네요.

 

어쨌든 결론은 Windows Kits 디렉토리에 설치되는 Windows Software Development Kit 나 Windows Driver Kit 를 같은 버전으로 유지해야 한다는 것입니다. 아니면 UnrealBuildTool 이 제대로 설치된 ucrt 경로를 찾아 주든가요... 아마 후자를 기대하기는 힘들 것 같습니다.

 

원문 : Introducing the Universal CRT

주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.


 

작년 6월에 우리는 한 쌍의 기사들을 발표했는데, 그것은 Visual Studio 2010 에서 Visual C++ C Runtime( CRT ) 에 대해 수행된 큰 변화에 대해서 다뤘습니다. "The Great C Runtime (CRT) Refactoring" 에서, 우리는 CRT 에 대해 수행한 중요한 구조적 변화를 설명했습니다. 그리고 "C Runtime (CRT) Features, Fixes, and Breaking Changes in Visual Studio 14 CTP1" 에서, 우리는 기존에 구현해온 중요한 기능들과 우리가 만들어 온 행동 변화들을 모두 애뮬레이트했습니다.

 

우리는 그 기사들을 작성하고 Visual Studio 2015 의 첫 번째 Community Technology Preview (CTP) 를 릴리스한 이후로 몇 개월 동안 여러분으로부터 많은 피드백을 받아 왔습니다. 우리는 특히 Microsoft Connect 에 여러분이 제출해 준 많은 탁월한 버그들에 대해서 감사드립니다. 첫 번째 CTP 이후로 CRT 에 많은 변화를 주지는 않았지만, 우리는 여러분의 피드백을 다루고, 좀 더 나은 결과를 이루고, 일부 오래 걸리는 프로젝트들을 마무리하기 위해 많은 일들을 해 왔습니다. 가장 최근에 릴리스된 Visual Studio 2015 CTP6 는 우리가 작업해 온 이러한 모든 개선점들을 포함합니다. 우리는 또 다시 한 쌍의 기사들에서 이러한 변화들에 대해서 다룰 계획입니다: 이 기사는 첫 번째 CTP 이후로의 중요한 구조적 변화들에 대해서 다룹니다; 다음 기사에서는 새로운 기능들, 버그 픽스들, 그리고 변화에 대한 세부사항을 모두 애뮬레이트할 계획입니다.

 

지난 6월의 기사들에서, 우리는 CRT 가 두 개의 논리 파트들로 나뉜 방식에 대해서 설명했습니다: VCRuntime 은 프로세스 시작과 예외 처리와 같은 것들을 위해서 요구되는 기능성들을 지원하는 컴파일러를 포함하고, "stable" 파트는 CRT 의 순수한 라이브러리 파트들을 포함합니다. 여기에서 우리는, Visual Studio 의 주요 버전과 함께 새롭게 버전이 설정되는 DLL 들을 릴리스하기 보다는, 앞으로는 in-place 로 서비스를 할 것입니다. 이 시점에 이 "stable" 파트는 두 개의 라이브러리 형태를 취했었습니다: AppCRT 와 DesktopCRT( 릴리스 DLL 들의 이름은 appcrt140.dll 과 desktopcrt140.dll 이었습니다 ).

 

VCRuntime 은 여전히 같은 형태로 존재하며, 기존의 CTP 들에서와 동일한 내용을 가지고 있습니다. 이 마지막 CTP6 에서 우리가 만든 중요한 변화들은 "stable" 파트 내부에 존재합니다. AppCRT 와 DesktopCRT 는 하나의 라이브러리로 다시 합쳐졌는데, 이는 Universal CRT 라 불립니다. 새로운 DLL 들은 ucrtbase.dll( 릴리스 ) 와 ucrtbased.dll( 디버그 ) 입니다; 그것들은 버전 넘버를 가지고 있지 않습니다. 왜냐하면 in-place 로 서비스될 것이기 때문입니다.

 

Universal CRT 는 윈도우즈 운영 체제의 요소입니다. 이것은 January Technical Preview 의 시작과 함께 Windows 10 의 일부로 포함되어 있으며, 윈도우즈 업데이트를 통해서 이전 버전의 운영 체제에서도 이용할 수 있습니다.

 

Universal CRT 를 사용하여 소프트웨어 빌드


이전에는 CRT 의 모든 헤더, 소스, 라이브러리가 Visual C++ SDK 의 일부로서 배포되었는데, 이는 Visual Studio 설치 디렉토리( 일반적으로 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC )의 VC 서브 디렉토리에 설치되었습니다. VCRuntime 을 위한 파일들은 여전히 Visual C++ SDK 의 일부로 존재합니다. 헤더, 소스, 라이브러리는 이제 개별적인 Universal CRT SDK 의 일부로 배포됩니다. 이 SDK 는 Visual Studio 에 포함되어 있습니다; 이는 기본적으로 C:\Program Files (x86)\Windows Kits\10 에 설치됩니다. 디버그용 ucrtbased.dll 은 여전히 이 SDK 의 일부로 포함되며, 시스템 디렉토리에 설치됩니다.

 

우리는 Visual C++ MSBuild props 및 targets 파일들을 업데이트하여 새로운 Universal CRT 디렉토리들을 include 및 library 경로에 추가했습니다. 만약 Visual Studio 2015 로 프로젝트를 생성하거나 Visual Studio 2015 로 현존하는 프로젝트를 업그레이드한다면, 그것은 이러한 새로운 디렉토리들을 자동으로 선택할 것입니다. 만약 Visual C++ MSBuild props 및 targets 를 사용하지 않거나 기본 include 및 library 경로를 그런 props 및 targets 파일에서 상속하지 않는 프로젝트를 업그레이드한다면, 여러분은 그 새 디렉토리들을 수동으로 업데이트해 줄 필요가 있습니다. 여러분은 Universal CRT SDK 파일들을 찾기 위해서 다음과 같은 MSBuild 속성들을 사용할 수 있습니다.

 

$(UniversalCRT_IncludePath)
$(UniversalCRT_LibraryPath_x86)
$(UniversalCRT_LibraryPath_x64)
$(UniversalCRT_LibraryPath_arm)

 

그러므로 여러분이 /nodefaultlib 옵션을 사용해 링크하지 않는 이상, 모든 correct 라이브러리 파일들은 여러분이 프로젝트를 링크할 때 검색될 것입니다. 만약 /nodefaultlib 옵션을 사용해서 링크한다면, 여러분은 링크를 할 때 몇 개의 추가적인 라이브러리들을 링크할 필요가 있을 것입니다. 예를 들어, 이전에는 CRT DLL 을 사용하기 위해서 msvcrt.lib 를 가지고 있었다면, 이제는 vcruntime.lib 와 ucrt.lib 도 링크할 필요가 있습니다. 아래 테이블에서는 어떠한 라이브러리가 이전의 라이브러리에 대응해 링크되어야 하는지를 보여 줍니다.

 

 Release DLLs  (/MD  ) :

 msvcrt.lib

 vcruntime.lib

 ucrt.lib

 Debug DLLs    (/MDd) :

 msvcrtd.lib

 vcruntimed.lib

 ucrtd.lib

 Release Static (/MT  ) :

 libcmt.lib

 libvcruntime.lib

 libucrt.lib

 Debug Static    (/MTd) :

 libcmtd.lib

 libvcruntimed.lib

 libucrtd.lib

 

Universal CRT 를 사용하는 소프트웨어 배포하기


과거에는, "Deployment in Visual C++" 에서 설명된 많은 방식 중 하나를 사용해서 Visual C++ 라이브러리들을 소프트웨어와 함께 배포해 왔을 것입니다. Universal CRT 를 제외한 모든 Visual C++ 라이브러리들에 대해, 배포 방식에서의 변화는 있을 수 없습니다. 배포 모드가 무엇이든 간에( central, local or static linking ) 이전 방식이 여전히 사용될 수 있습니다.

 

그러나 위에서 언급했듯이 Universal CRT 가 윈도우즈 운영 체제로 이동했기 때문에, 약간의 주목해야할 변화들이 존재합니다:

 

  1. Universal CRT 는 윈도우즈 운영 체제 요소입니다. 이는 윈도우즈 10 의 일부입니다. Windows 10 보다 앞선 버전의 윈도우에 대해서도, 윈도우즈 업데이트를 통해서 Universal CRT 가 배포될 수 있습니다. 윈도우즈 Vista 부터 Windows 8.1 까지를 위한 윈도우즈 업데이트 MSU 패키지들이 있습니다. 현재 MSU 패키지들은 VCRedist 설치의 일부로서 설치됩니다. 이후의 Visual Studio 2015 빌드에서는, 이러한 MSU 패키지들이 Universal CRT SDK 의 일부로서 개별적으로 배포될 계획이며, 다운로드를 위해서 support.microsoft.com 을 이용할 계획입니다.
  2. 만약 Universal CRT 가 설치되어 있지 않은 윈도우즈 운영 체제( 즉 윈도우즈 8.1 이전 )에서 사용되도록 설계된 소프트웨어를 빌드했다면, 여러분의 소프트웨어는 Universal CRT 를 설치하기 위해서 위에서 언급했던 윈도우즈 업데이트 패키지에 의존해야할 필요가 있을 것입니다.
  3.  만약 현재 VCRedist( 우리의 재배포 패키지 파일들 ) 를 사용하고 있다면, 기존에 그랬던 것처럼 잘 동작할 것입니다. Visual Studio 2015 VCRedist 패키지는 위에서 언급했던 윈도우즈 업데이트 패키지를 포함하고 있어서, 단순히 VCRedist 를 설치하는 것만으로도 Visual C++ 라이브러리들과 Universal CRT 를 설치할 것입니다. 이는 우리가 추천하는 배포 메커니즘입니다. 윈도우즈 XP 에 대해서는 Universal CRT 윈도우즈 업데이트 MSU 가 존재하지 않으므로, VCRedist 가 Universal CRT 자체를 배포할 것입니다.
  4. 만약 현재 Visual C++ 라이브러리들을 정적으로 링크하고 있다면, 기존에 그랬던 것과 같이 잘 동작할 것입니다. 우리는 성능 및 서비스 유용성을 이유로 Visual C++ 라이브러리들에 대한 정적 링크를 강력하게 권장하고 있습니다. but we recognize that there are some use cases that require static libraries and we will continue to support the static libraries for those reasons.
  5. Universal CRT 에 대한 머지( merge ) 모듈은 존재하지 않을 것입니다. 만약 현재 CRT 머지 모듈을 사용하고 있고, 여전히 Visual C++ 라이브러리들을 중앙에서 배포하기를 원한다면, 우리는 여러분이 위에서 언급된 윈도우즈 업데이트 패키지나 VCRedist 로 이동할 것을 권장합니다. 대안적으로, 여러분은 Universal CRT 와 Visual C++ 라이브러리를 정적으로 링크하는 선택을 할 수도 있습니다.
  6. Universal CRT 에 대한 app-local 배포가 지원됩니다. app-local 배포를 위한 바이너리를 획득하고자 한다면, Windows Software Development Kit (SDK) for Windows 10 을 설치하십시오. 이 바이너리들은 C:\Program Fiels (x86)\Windows Kits\10\Redist\ucrt 에 설치될 것입니다. 여러분은 모든 DLL 들을 당신의 app 에 복사할 필요가 있습니다( DLL 집합은 서로 다른 버전의 윈도우즈에 대해서 다릅니다. 그러므로 지원되는 모든 버전의 윈도우에서 프로그램이 실행되도록 하기 위해서는 모든 DLL 들을 포함시켜야만 합니다 ).

이전에 언급했던 것처럼, 우리는 리팩토링된 CRT 를 CTP1 에서 소개한 이후로 많은 버그를 해결하고 기능을 개선해 왔습니다. 이번 주 후반에는 이러한 변경들에 대해서 더 세부적으로 다루는 두 번째 기사를 작성할 계획입니다. 그 동안에, 우리는 새로운 Universal CRT 에 대한 여러분의 피드백을 기대하도록 하겠습니다.

 

James McNellis and Raman Sharma

Visual C++ Libraries

주의 : 공부하면서 정리한 것이라 잘못된 내용이 있을 수 있습니다.


이 글은 [ UE4 캐릭터 이동 시스템 가이드 ]의 보충 문서입니다. 


UCharacterMovementComponent 의 속성은 여러 개의 카테고리로 나뉘는데, 이 문서에서는 "MovementMode" 카테고리에 대해서 다룹니다. 말 그대로 걷거나 달리는 것과 관련한 행위를 제어합니다.


이것과 관련한 속성으로는 다음과 같은 것들이 있습니다( Desc 항목은 주석을 그대로 옮겨 놓은 것입니다 ).


 Property

 Specifier

 Desc

 MovementMode

 BlueprintReadOnly

 액터의 현재 이동 모드( walking, falling 등 ).

  - walking: 표면 위를 걷는데, 마찰력 효과를 가지며, 방해물을 걸어 올라갈 수 있음. 수직 속력은 0 임.

  - falling: 중력 효과를 받아 떨어지는데, 점프하거나 표면의 가장자리에서 발이 떨어지는 상황임.

 - flying: 중력 효과를 무시하고 날아감.

 - swimming: 유체 덩어리를 통해서 수영하고 있는데, 중력 효과와 부심( bouyancy ) 효과의 영향을 받음.

 - custom: 사용자 정의 커스텀 이동 모드. 매우 많은 서브 모드를 포함할 수 있음.

 이는 Character owner 를 통해 그리고 client-server 이동 함수들을 위해 자동으로 복제된다.

 CustomMovementMode

 BlueprintReadOnly

 MovementMode 가 Custom 일 때 현재의 커스텀 서브 모드를 지정함.

 이는 Character owner 를 통해 그리고 client-server 이동 함수들을 위해 자동으로 복제된다.


주석 자체가 매우 많은 설명을 담고 있기 때문에 뭐라 더 설명할 것이 없을 것 같습니다.


요는 정해진 4 개의 이동 모드가 있고, 사용자가 원한다면 자신만의 커스텀 모드를 설정할 수 있다는 것입니다. 예를 들어 벽을 기어 오르는 모드라든가 매달리는 모드같은 것을 생각해 볼 수 있습니다. 매달리는 상황에서는 매달리는 손들을 기준으로 진자운동을 하며 ( 허리 이하의 ) 일부 본들은 랙돌 상태의 영향을 받는 것이 좋겠죠. 중력을 계산할 필요는 없을 것이구요. 이는 분명히 기존의 4 개의 이동 모드의 동작과는 다릅니다.


각각의 모드는 제약( constraint )를 가집니다. 만약 여러분이 RootMotion 을 만들었는데, 그 중에 z 축 방향으로 점프하는 동작이 포함되었다고 합시다. 그리고 이를 그냥 재생해 봅니다. 그러면 이상하게도 위로는 올라가지 않고 제자리에서 깔짝대기만 합니다. 이는 walking 모드에서는 z 축 성분을 무시하기 때문입니다( 정확하게는 애니메이션의 z 축 성분을 무시하는 것이 아니라 컨트롤러의 z 축 성분을 무시하는 것입니다 ).


RootMotion 이 아닐 때는 위로 점프하는 것이 제대로 동작할 것입니다. 이는 RootMotion 이 아닌 경우에는 컨트롤러가 바닥에 붙어 있기 때문입니다. 위의 표에서 walking 의 설명을 보십시오. "수직 속력이 0" 이라고 되어 있고, "표면위를 걷는다"고 되어 있습니다. 이러한 이동 모드의 특징을 제대로 이해해야 원하는 동작을 제대로 구현할 수 있을 것입니다.


이동 모드를 변경하는 것은 간단합니다. UCharacterMovementComponent 에는 SetMovementMode() 라는 메서드가 있습니다. 캐릭터에서 Jump() 같은 메서드를 호출하는 것은 내부적으로 이동모드의 변경을 내포하고 있습니다.



MovementMode 속성과 CustomMovementMode 속성을 설정할 수 있는 메서드입니다.

주의 : 공부하면서 정리한 것이므로 잘못된 내용이 포함되어 있을 수 있습니다.


개요



UE4, Unity 5, Frostbite, CryEngine 등과 같은 유명한 상용엔진들로 만든 게임들은 이제 기본적으로 PBR( Physically-Based Rendering )을 택하고 있습니다. 많은 AAA 급 타이틀들이 PBR 의 우수함을 증명했습니다. 우리나라야 이제 게임 기술 후진국이 되어 가고 있기 때문에( 주관적 판단임 ) 인기가 시들하고 개념을 모르는 사람도 많지만, 카더라 통신에 의하면 중국마저도 다들 PBR 에 꽂혀 가고 있다고 하더군요.


아티스트가 PBR 을 처음 접하면 매우 어렵습니다. 일종의 라이팅 패러다임의 변화이기 때문입니다. 그러나 조금 익숙해지면 기존의 라이팅 모델들보다 훨씬 적은 매개변수만을 사용해 라이팅을 제어할 수 있어서 PBR 을 선호하게 됩니다. 그리고 라이팅 환경이 바뀌어도 일관된 결과를 보여 준다는 점에서 매우 매력적입니다.


하지만 프로그래머에게 있어서는 지옥의 시작입니다. 모르는 단어들도 수두룩하게 나오는 데다가, 비용도 비싸기 때문에 최적화에 어려움을 겪게 됩니다. 게다가 자료도 그리 많지 않은 편입니다. 구글링을 해 보시면 알겠지만, PBR 구현에 대해 명료하게 다루는 글들이 많지 않습니다. 특히 PBR 구현에 있어 대세인 Cook-Torrance 에서 사용하는 GGX 라는 것은 검색해도 개념을 설명하는 글들을 찾기 힘듭니다. 


그래서 이 문서에서는 GGX 라는 것이 어떻게 나오게 된 것인지 가이드를 제공하고자 합니다. GGX 에 대해서 파고 들어 설명하는 글은 아닙니다. 이것을 이해하기 위해서 봐야 될 문서를 정리했다고 생각하시면 됩니다( 제가 실무를 통해 직접 구현해 본 것이 아니기 때문에 이해도가 낮습니다 ).


Cook-Torrance



참고 자료 : Cook-Torrance

참고 자료 : Specular BRDF Reference


우리가 기존에 다루었던 라이팅 모델들은 대부분 어느 정도는 미세면( microfacet )에 대해서 고려하고 있습니다. 이 표면의 거칠기를 어떻게 근사계산하느냐에 따라서 여러 가지 모델들이 나뉘게 됩니다. 이 거칠기에 의해서 reflectance model 이 결정되는데요, 아마 PBR 을 검색해서 이 문서를 찾으실 정도의 분들이라면 BRDF( Bidirectional Reflectance Distribution Function )의 개념에 대해서는 이해하고 계실 것이라고 가정합니다. 


만약 BRDF 가 뭔지 모르겠다는 분들이 있다면 그것부터 공부하고 오시는 것이 좋을 것이라 생각합니다. 그렇지 않다면 이 문서를 더 봐도 무슨 말을 하는지 이해하기 어려울 수 있습니다. 어쨌든 미세면 개념은 매우 중요합니다. 그리고 이와 관련한 많은 쉐이딩 이론이 존재합니다. 하지만 여기에서는 Cook-Torrance 모델에 대해서만 다루도록 하겠습니다.


Cook-Torrance 는 Robert L. Cook 과 Kenneth E. Torrance 에 의해 개발된 라이팅 이론입니다. 이는 Phong 이나 Blinn-Phong 보다는 좀 더 물리적인 현실감을 제공합니다. 이 모델은 오브젝트의 표면이 매우 많은 미세면에 의해 구성되어 있다고 가정합니다. 이 미세면들은 개별적으로 들어 오는 빛을 반사하게 되는데요, 이것이 거치냐 매끄럽냐에 따라서 반사하는 패턴이 달라지게 된다는 것입니다. 물론 실제로 그것을 다 계산해 주면 좋겠지만, 컴퓨팅 파워 및 메모리의 부족으로 인해 분산함수를 사용하게 됩니다.


Cook-Torrance 에서는 대부분의 쉐이딩 모델과 마찬가지로 ambient, diffuse reflection, specular reflection 항을 사용합니다. 그 중 diffuse reflection 을 위해서는 Lambert reflection( N dot L )을 사용합니다. 대부분의 계산은 specular reflection 에 초점을 맞추고 있습니다. 이 모델의 최종 공식은 다음과 같습니다.



여기에서 k 는 diffuse 계수입니다. ( 1 - k ) 가 들어 가는 것은 에너지 보존을 위해서입니다. 그리고 rs 는 specular 항입니다.


이 specular 항은 다음과 같이 계산됩니다.



F 는 Fresnel 항이고, D 는 Distribution 항이고, G 는 Geometric shadow( Geometric attenuation ) 항입니다. 각 항에 대한 자세한 설명은 [ Cook-Torrance ]에서 확인하시기 바랍니다. 


이제 각 항에다가 어떤 공식을 넣느냐에 따라서 변종이 발생합니다. 그 변종들의 공식에 대해서는 [ Specular BRDF Reference ] 라는 기사에 잘 정리되어 있습니다. 이 중에 요새 GGX 가 대세라 하더군요. 


GGX?



참고 자료 : Microfacet Models for Refraction through Rough Surfaces


GGX 라는 것은 [ Microfacet Models for Refraction through Rough Surfaces ] 에서 처음 소개된 것으로 보입니다( 번역 : http://lifeisforu.tistory.com/352 ). 이 논문의 저자로 Kenneth E. Torrance 가 포함되어 있네요.


We also introduce a new microfacet distribution, which we call GGX, that provides a closer match for some of our surfaces than the standard Beckmann distribution function.


출처 : Microfacet Models for Refraction through Rough Surfaces


 

말 그대로 이 논문에서 새로운 미세면 분산 함수인 GGX 를 소개했습니다. 그런데 이 문서에서도 "GGX 가 뭐의 약자이다" 라는 식의 이야기는 없습니다. 그냥 제가 추측하기로는 shadow visibility 를 구할 때 G1 을 두 번 곱하게 되는데, 그래서 GG 가 아닐까 싶네요. X 는 eXtended multiply 정도의 느낌이랄까... 이것에 대해 정확하게 아시는 분이 있다면 댓글 달아 주시면 좋겠습니다.


수정: 댓글달아 주신 3D 님의 "Ground Glass Unknown" 을 힌트로 검색을 해 봤는데, 이게 GGX 가 맞더군요. http://jcgt.org/published/0007/04/01/paper.pdf


어쨌든 이 GGX 에 대한 완벽한 이해를 원하신다면 해당 문서를 보시는 것이 좋을 것 같습니다.


그리고 GGX 최적화를 위해서 [ Optimizing GGX Shaders with dot( L, H ) ] 라는 글도 참고해 보실만 할 것 같습니다. 수정 : 실제로 적용해 본 분에 의하면, instruction 수 차이도 별로 안 나고 오류가 있다고 하네요. 그래서 적용했다가 뺐다고 하는데, 자세한 내용은 기억이 안 난다고 합니다. 수정2 : 다른 분이 이 방식의 옵티마이즈를 해 보고 반론을 주셨습니다. 아래 카톡 대화를 참조하세요.

 

 

또한 [ Adopting a Physically-based Microfacet BRDF model in Three.JS ] 라는 글도 읽어 보시면 도움이 될 것 같습니다. 링크의 페이지에서는, 웹에서 PBR 을 구현하면서 만나게 된 여러 가지 이슈들에 대해 정리해 놓았습니다.


추가: UE4 문서 같은데 보면 lighting model 은 light-source 를 모델링하는 것을 의미하더군요. 예를 들어 area light 를 위해 billboard-reflections, cone-intersection, representive-point, sphere-lights, tube-lights 등의 모델을 사용할 수 있습니다( http://lifeisforu.tistory.com/348 의 lighting model 항목을 참조하세요 ).

주의 : 다른 방법으로도 만들 수 있으니 이 자료를 맹신하지 마시기 바랍니다.

주의 : 공부하면서 정리한 것이기 때문에 잘못된 내용이 있을 수 있습니다.

주의 : Max 2013 과 2015 에서 테스트했습니다.

주의 : 맥스 스크립트는 이번에 처음 만들어 봅니다. 이상한 부분이 있을 수 있으니 양해 부탁드리고 문제점이 있으면 지적해 주십시오.


개요



언리얼의 [ 루트 모션 ]은 "Root" 라는 이름을 가진 본이 스켈레톤 구조의 최상위에 위치할 것을 요구합니다. 


그런데 우리 나라의 경우에는 Biped 만을 사용하기 때문에 최상위 본은 항상 "Bip01" 입니다. 애니메이터에게 물어 봤더니 Biped layer 같은 것을 사용하려면 Bip01 본 상위에 뭔가 붙여서는 안 된다고 하더군요. 


외국물 좀 먹은 다른 애니메이터에게 물어 봤더니 쉐도우 리깅이나 스크립트를 사용해서 루트 모션을 만들어 낸다고 합니다. 그리고 그 애니메이터의 경우에는 Biped 를 사용하지 않아도 애니메이션 만들 방법은 많기 때문에 굳이 그런 고민을 안 한다고 합니다( 그리고 마야를 많이 쓴다고 하더군요 ).


애니메이터가 Biped 에서 루트 모션을 만드는 과정을 관찰했더니 이건 완전히 생노가다였습니다. 그런데 우리나라 애니메이터들은 Biped 이외에는 사용할 생각이 없기 때문에, 이를 맥스스크립트로 자동화시켜야겠다는 생각을 했습니다.


맥스 스크립트



루트 모션을 만드는 것을 관찰했더니 순서는 [ 다음 ] 과 같았습니다( 링크의 문서는 우리 회사 캐릭터 팀장이 작성했습니다. 물론 위에서 언급했듯이 이게 루트 모션을 만드는 유일한 방법은 아닙니다  ).


저는 그 과정을 보고 최종적으로 "Root 본이 Bip01 의 TM 을 따라 가고, 반대로 Bip01 은 Root 본에 종속되어 상대적인 TM 을 가지면 된다" 라는 결론을 내렸습니다. 그래서 다음과 같은 알고리즘을 세웠습니다.


  1. 제거할 축 입력받음.
  2. "Root" 본 생성.
  3. "Bip01" 본 획득.
  4. Root 본에 Position_XYZ 컨트롤러 바인딩하고 프레임 개수만큼 키를 생성.
  5. Bip01 을 따라 가는 Position_Constraint 컨트롤러 "tempController" 생성.
  6. 전체 프레임을 돌면서 "tempController" 값을 Root 본에 복사함( Bip01 을 복사하는 것과 동일함 ).
  7. Biped 에 "RootMotionLayer" 레이어를 생성하고 현재 레이어로 설정함.
  8. 새로운 레이어의 position 컨트롤러를 획득해서 전체 프레임 개수만큼 키를 생성.
  9. 새로운 레이어의 키의 position 에 "tempController" 컨트롤러에서 특정 축값을 제거한 값을 삽입.
  10. Root 본을 Bip01 의 부모로 설정.
  11. FBX 로 익스포트( 수동 ).
  12. 익스포트 후에 "RootMotionLayer" 레이어 및 "Root" 본 제거( 수동 ).


5 ~ 10 작업은 Bip01 이 Root 에 대해 상대적인 TM 을 가지도록 블렌딩해 주는 작업입니다. 제가 Biped 에 대해서 잘 모르기 때문에 자세한 것은 설명해 드리기 힘들고, 궁금한 것은 자기 팀 애니메이터에게 문의하시면 될 것 같습니다. 



스크립트는 다음과 같습니다. 수정 : Biped Root 의 이름을 입력받을 수 있게 했습니다. 수정 : minZ 와 maxZ 를 입력받을 수 있게 했습니다. 수정 : 바닥이 될 본을 설정할 수 있게 했습니다. 이는 RemoveZ 가 꺼졌을 때만 활성화됩니다.



한계 및 할 일들



애니메이터의 이야기를 들어 보니 점프를 하거나 비대칭적인 캐릭터( 예를 들어 기린? )를 작업할 때는 직접 Dummy 를 만들어서 컨트롤러의 위치를 지정한다고 합니다. 그러므로 그런 경우에는 Root 본이 Bip01 이 아니라 Dummy 를 따라 가도록 만들어야 할 것입니다. 다이얼로그에 그런 옵션들을 추가로 넣고 코드를 수정할 수 있을 것 같습니다. 


그리고 알고리즘의 11 번이나 12 번을 자동화할 필요도 있을 것 같습니다. 아니면 차라리 새로운 파일을 자동으로 만들어서 거기에서 작업이 수행되도록 할 수도 있겠죠.


그리고 현재 스크립트는 항상 position 만을 처리하고 있는데, rotation 이나 scaling 을 처리해야 할 수도 있습니다.


수정 : 그리고 공중회전같은 것을 하면 현재 스크립트의 결과물은 이상해질 것입니다. 이럴 경우에는 다른 방법을 찾아야 합니다.


기회가 되면 루트 모션을 만드는 여러 가지 방법들에 대해서 연구해 봐야할 것 같습니다.

주의 : 공부하면서 정리한 것이기 때문에 잘못된 내용이 있을 수 있습니다.


개요



언리얼 엔진에서 캐릭터를 다루기 위해서 알아야 할 가장 중요한 컴포넌트가 무엇이냐고 질문을 하신다면, 저는 "가장 중요한 것은 UCharacterMovementComponent 입니다" 라고 대답을 할 것입니다.


대부분의 사람들이 이 컴포넌트를 아무 생각없이 사용하고 있지만 이 컴포넌트의 동작을 제대로 이해하지 못한다면 많은 문제에 부딪히게 됩니다. 


튜토리얼같은 것들을 따라 할때는 제한된 조건에서 캐릭터를 움직이기 때문에 이것을 이해해야 할 필요성을 별로 느끼지 못합니다. 하지만 복합적인 상황을 고려하면 반드시 이해해야만 합니다.


몇 가지 간단한 예를 들어 보도록 하겠습니다.

    • RootMotion 을 실행하고 있을 경우에 ACharacter::LaunchCharacter() 류의 메서드 호출이 제대로 동작하지 않습니다.
    • AIController 를 연결했을 경우에 AController::SetControlRotation() 류의 메서드 호출이 제대로 동작하지 않습니다.


결국 조금만 복잡한 상황을 처리하거나 깊이 들어 가면 CharacterMovement 컴포넌트의 동작을 제대로 이해하지 않고서는 작업을 진행하기 어려운 상태가 된다는 것입니다. 그리고 이는 작업의 방향에도 큰 영향을 끼칩니다.


위에서 예로 들었던 RootMotion 을 생각해 봅시다. 만약 idle 이나 walk 를 RootMotion 으로 만들었다면, 플레이어 캐릭터가 몹을 때렸을 때 밀려나게 만들 수 없을 것입니다. 그러므로 그런 상황을 가정했다면 idel 이나 walk 를 RootMotion 이 아니도록 만들어야만 합니다. 아니면 자신만의 CharacterMovement 컴포넌트를 구현해야합니다.


이러한 문제점들을 해결하기 위한 목적 이외에도, CharacterMovement 컴포넌트는 RVO Avoidance System, Navigation System, AI system, Animation System, Physics System, Input System 등과 연결되어 있기 때문에 잘 이해해야 할 시스템입니다.


저도 이 컴포넌트에 대한 이해없이 그냥 예제 보면서 따라하다가 무의미하게 시간만 낭비했습니다. 그래서 이 컴포넌트에 대한 이해도를 높여 보려는 목적으로 이 문서를 작성하게 되었습니다. 부족함이 많은 글이겠지만, 모쪼록 처음 접하는 사람들에게 도움이 되었으면 합니다.


이 문서에서는 모든 요소들에 대해서 세부적으로 다루지는 않습니다. 코드를 분석하는데 도움이 될 수 있는 가이드를 제공할 뿐입니다. 그리고 여기에서는 standalone 으로 플레이한다는 가정을 깔고 있습니다. Client-Server 모델에서는 약간 동작이 다른데 그 부분은 알아서 분석하시기 바랍니다.


다음과 같은 참고 문서들을 확인하시기 바랍니다.


기본 : 속도와 가속도



플레이어가 1초 후에 X 축으로 1 미터 앞에 있기를 원한다고 합시다. 그러면 이 목표를 달성하기 위한 메서드를 만들어야 합니다. 간단하게 생각하면 UCharacterMovement::SetLocation( const FVector& NewLocation, const float Time ) 과 같은 메서드를 만들 수 있습니다. 이것을 기획자가 사용한다면 매우 멋진 인터페이스가 될 것입니다.


하지만 이것을 실제 구현하고 있다고 생각해 봅시다. 순간이동이 아니기 때문에 0 초와 1 초 사이의 무수히( ? ) 많은 프레임에서의 위치를 계산해야 합니다. 등속도 운동을 해야 할까요? 아니면 등가속도 운동을 해야 할까요?


혹은 중간에 뭔가 장애물이 있다고 합시다. 이것을 어떻게 처리해야 할까요?


결국 월드와의 상호작용을 고려한다면 일관된 법칙을 가질 필요가 있습니다. 이미 PhysicsX 라는 물리 엔진을 기반으로 깔고 가고 있기 때문에 이에 맞추는 것이 여러모로 편할 것입니다. 


CharaterMovement 컴포넌트의 기본은 velocity( 속도 ) 와 acceleration( 가속도 ) 을 제어하는데 있습니다. 최종적으로 이를 통해서 캐릭터의 Location, Rotation 이 결정됩니다. 그리고 수많은 시스템들이 속도와 가속도를 결정하는데 영향을 주게 됩니다.


기억이 잘 안 나는 분들이 있을 것이기 때문에 기본적인 뉴턴 운동법칙에 대해서 복습해 보도록 하겠습니다. 뉴턴 운동법칙은 크게 3 가지 법칙으로 나뉩니다.

  • 1법칙 : 관성의 법칙.
  • 2법칙 : 가속도의 법칙.
  • 3법칙 : 작용과 반작용의 법칙.


우리가 주목할 것은 가속도의 법칙입니다. 그 정의는 다음과 같습니다.


물체의 운동량의 시간에 따른 변화율은 그 물체에 작용하는 알짜힘과 ( 크기와 방향에 있어서 ) 같다.


출처 : 뉴턴 운동 법칙, 위키피디아.


이를 수식으로 쓰면 다음과 같습니다. 여기에서 F 는 알짜힘, P( = mv ) 는 운동량, m 은 질량, v 는 속도, a 는 가속도입니다.




일반적으로 질량이라는 것이 시간에 따라 변하지 않으므로 다음과 같이 식을 작성할 수 있습니다.




이제 우리가 어떤 물체를 움직이는 함수를 작성한다고 해 봅시다. 그러면 하나의 값을 사용해서 물체를 움직일 수 있습니다. 그것은 "힘"입니다.



UCharacterMovementComponent::AddForce() 메서드의 7 라인에서 Force 를 Mass 로 나누는 것을 볼 수 있습니다. 결국 PendingForceToApply 라는 것은 가속도를 의미하게 됩니다.


이제 실제 이 가속도는 어디에서 사용되는 것일까요? 다음과 같은 호출순서에 의해 가속도가 처리됩니다.



그림 1


3 번 호출의 내용을 보면 다음과 같습니다.



12 라인의 PendingForceToApply * DeltaSeconds 를 통해 Velocity 를 얻어 내고 있습니다. 가속도라는 것은 초당 속도의 변화량이기 때문에 DeltaSeconds 를 곱하는 것이죠. 만약 가속도가 100 이라면 1 초 후에는 속도가 100 이 증가되기를 원한다는 의미가 됩니다. 그러므로 0.1 초 동안 증가한 속도는 10 입니다( 이 설명은 왜 하고 있는지 ㅡㅡ;; ).


어쨌든 이런 식으로 캐릭터의 이동을 처리하게 됩니다. 보통 입력시스템과 관련한 플루프린트에서는 AddForce() 를 호출하게 됩니다.


본 주제로 돌아 가서 1초 동안 1 미터를 이동하려면 어떻게 해야 할까요? 이 경우에는 복잡한 계산이 필요합니다. 보통 캐릭터 입력은 등가속도 운동과 등속도 운동이 모두 포함되기 때문에 단순하게 계산하기 어렵습니다( 미적분이 필요합니다 ). 그러므로 이는 기획자가 사용하기에는 적절하지 않습니다. 프로그래머나 물리를 좀 아는 기획자가 미리 테이블을 만들어서 전달해 줄 필요가 있습니다. 아니면 기획자가 감으로 해 보는 수밖에 없습니다.


여기에서는 설명을 단순화하기 위해서 마치 속도가 한 메서드 내부에서만 계산되는 것처럼 설명했습니다. 하지만 실제로는 매우 많은 메서드 내에서 이 속도를 제어하게 됩니다. 앞에서도 언급했듯이 많은 다양한 시스템들의 영향을 받게 됩니다.


UpdatedComponent



CharaterMovement 컴포넌트가 실제로 제어하는 것은 어떤 요소일까요?


Character 의 경우에는 자신의 RootComponent 를 CharacterMovement 컴포넌트가 제어해야 하는 요소로 지정하고 있습니다.



위의 생성자의 13 라인을 보면 UpdatedComponent 에 CapsuleComponent 를 할당하고 있는 것을 볼 수 있습니다. 즉 CharacterMovement 컴포넌트는 루트 컴포넌트를 제어하게 된다는 의미입니다.


이제 CharacterMovement 컴포넌트 내에서는 UpdatedComponent 에 접근해서 이런 저런 일들을 수행하게 됩니다. 예를 들어 다음과 같은 메서드가 있습니다( UCharacterMovementComponent 는 궁극적으로 UMovementComponent 를 상속하고 있습니다 ).



PerformMovement



캐릭터의 최종 위치를 결정하는 핵심 메서드는 UCharacterMovementComponent::PerformMovement() 입니다. 여기에서는 여러 가지 입력을 평가해서 UpdatedComponent 에 그 결과를 반영합니다.


그림 2


전체적으로 크게 이동 처리와 회전 처리로 구분됩니다. 그림 2 의 3, 4, 5, 6 은 상황에 맞게 여러 가지 입력으로부터 속도를 계산합니다. 그리고 7 은 현재 상황( walking, jumping )에 맞게 이동 계산을 수행합니다. 그리고 8, 9 에서는 회전 계산을 수행합니다. 그리고 나서 10, 11 에서 이벤트를 처리합니다.


StartNewPhysics() 에서는 MovementMode 에 따라 다음과 같이 호출을 분기시켜 줍니다.


 MovementMode

 관련 Method

 비고

 MOVE_None

 

 

 MOVE_Walking

 PhysWalking()

 

 MOVE_NavWaling

 PhysNavWalking()

 

 MOVE_Falling

 PhysFalling()

 Jump 및 Launch 에 의해 실행됨

 MOVE_Flying

 PhysFlying()

 

 MOVE_Swimming

 PhysSwimming()

 

 MOVE_Custom

 PhysCustom()

 


RootMotion



RootMotion 과 CharacterMovement 컴포넌트의 관계를 잘 이해할 필요가 있습니다.


RootMotion 이라는 것은 애니메이션의 Root 본을 기준으로 캐릭터를 제어하는 모드를 의미합니다. 자세한 내용은 언리얼의 [ Root Motion ] 을 참고하시면 됩니다( 원래는 한국어 번역도 있었던 것 같은데, 지금은 한국어만 안 되네요 ).


RootMotion 모드일 때 캐릭터의 위치을 애니메이션 기준으로 제어해야 하기 때문에, 그림 2 의 5, 6 을 통해서 속도를 계산합니다. 


SkeletalMesh 의 pose 는 USkeletalMeshComponent::TickPose() 에 의해 결정됩니다. 그런데 일반적으로는 이것이 USkinnedMeshComponent::TickComponent() 에 의해 호출되지만, RootMotion 일 때는 UCharacterMovementComponent::TickComponent() 에 의해 호출된다는 차이가 있습니다. 이는 원하는 시점에 정확하게 애니메이션을 시뮬레이션하기 위함이 아닌가 싶습니다.


RootMotion 의 경우에 5, 6 을 처리한다는 것은, 캐릭터에 대한 입력이 애니메이션인 경우라고 생각하시면 될 것 같습니다.


AI



아래 블루프린트는 캐릭터를 초당 한 번씩 회전시키려는 의도로 만들어졌습니다.



만약 AIController 를 사용하는 것이 아니라 일반 PlayerController 를 사용하고 있다면 위의 코드는 정상적으로 동작합니다. 그런데 AIController 만 붙이면 해당 호출이 무시됩니다.


그 이유를 구현 측면에서 좀더 파 보자면, AAIController::UpdateControlRotation() 에서 AAIController::SetControlRotation() 를 호출하고 있기 때문입니다. 그래서 블루프린트 내의 호출이 무시되는 것입니다.



AIController 는 FocalPoint 나 FocusActor 로부터 회전을 산출하도록 설계되어 있기 때문에, 이런 문제가 발생하는 것입니다. 결국 사용자가 각 컨트롤러의 구현 방식을 이해하지 못했다면 CharacterMovement 컴포넌트의 제어에 어려움이 발생합니다.


결론



CharacterMovementController 는 기본적으로 속도를 계산해 UpdateComponent 에 반영해 주는 일을 수행합니다. 그 과정에서 여러 요소들이 간섭을 하게 됩니다.


이 문서는 그러한 모든 요소들에 대해 설명하고 있지는 않습니다. 하지만 캐릭터 이동에 대해 제대로 이해하기 위해서는 이 컴포넌트와 다른 요소들이 어떻게 상호작용하는지 이해할 필요가 있습니다.


이 문서를 통해 캐릭터 이동이나 회전이 우리의 의도와는 다르게 동작할 수 있다는 점을 이해하고 그러한 경우에 어떻게 대처해야 하는지를 이해할 수 있다면, 이 문서를 작성한 의도의 대부분은 달성한 것이 아닐까 싶습니다.


시간이 되면( ? ), 다음에는 CharacterMovement 컴포넌트가 다른 요소들과 어떤 식으로 상호작용하는지에 대한 내용을 다뤄 볼 계획입니다.

개요



언리얼 엔진을 커스터마이징하거나 플러그인같은 것들을 제작하기 위해, 가장 먼저 이해해야 할 것은 언리얼의 빌드 시스템입니다.


물론 언리얼 엔진의 공식 문서에는 [ 언리얼 빌드 시스템 ] 이라는 항목이 존재하지만, 각 주제들이 개별적으로 설명되고 있기 때문에, 처음 접하는 입장에서는 종합적으로 이해하기 어렵습니다.


저도 이런 부분때문에 고생을 많이 했고, 이번 기회에 제대로 정리를 해 볼 생각입니다. 모쪼록 이 문서가 많은 사람들에게 도움이 되었으면 합니다.


이 문서에서는 빌드시스템의 각 항목에 대한 세부적인 내용을 다루기 보다는, 전체적인 윤곽을 보여 주고 문제를 해결하기 위해서 어떤 문서를 찾아야 하는지에 대한 가이드를 제공하려고 합니다.


여러분이 이 문서를 읽고 나면, 다음과 같은 것들을 할 수 있어야 합니다. 만약 그렇지 못하다면 제가 글을 잘 못쓰거나 누락한 부분이 있는 거겠죠.


  • 커스텀 빌드를 배포하기 위해서 소스 제어에 어떠한 것들을 등록해야 하는지 알 수 있습니다.
  • GenerateProjectFiles.bat 를 어떠한 상황에서 사용하는지 알 수 있습니다.
  • 언리얼 엔진의 프로젝트 구성에 대해 이해할 수 있습니다.
  • 모듈의 의미를 이해하고 사용할 수 있습니다.
  • 언리얼 엔진을 수정하고 배포할 수 있습니다.


이 문서는 "UnrealEngine 4.10.0-release" 와 윈도우즈 시스템을 기반으로 작성되었습니다.


참고로 이 문서에서는 자동화와 배포( automation & depolyment )에 대해서는 다루지 않습니다. 이건 빌드시스템과는 좀 다른 영역이라 나중에 기회가 되면 다루도록 하겠습니다.


공부하면서 정리한 거라서 잘못된 내용이 있을 수 있으므로, 이를 감안하고 보시기 바랍니다. 잘못된 내용들은 수정해 가도록 하겠습니다.


1. 소스 제어 - 기본 데이터 등록



언리얼 엔진의 소스를 받는 방법은 [ 언리얼 엔진 소스 코드 내려받기 ]에 나와 있습니다. 그런데 언리얼 엔진의 소스를 받고 나면 어떠한 것들을 소스 컨트롤에 등록해야 하는지 알기 어렵습니다. 어떤 파일들이 공유되어야 하고 어떤 파일들이 개인용 파일들인지 모르기 때문입니다. 뭘 올려야 하는지에 대한 설명도 제대로 없습니다.


언리얼 엔진의 소스를 받으면 다음과 같은 파일들이 생성됩니다.각각의 항목에 대한 자세한 내용은 [ 디렉토리 구조 ] 문서에 잘 나와 있습니다.



일단 소스를 받은 상황에서 아무 것도 하지 마시고, 1 번 항목을 제외하고는 통째로 소스 제어에 등록하시면 됩니다. 저는 git 을 안 쓰기 때문에 1번 항목을 지워버렸습니다.


2. Setup.bat



이제 Setup.bat 를 실행해서 의존성 파일들을 설치합니다. 1 번을 통해 소스 제어에 등록된 파일들을 다운로드받은 ( 프로그래머가 아닌 ) 작업자들도 반드시 이를 실행해야만 합니다.


나중에 자동화와 배포에 대해서 정리할 기회가 있으면 그 때 다시 언급하겠지만, 만약 작업자 머신에서 이 배치파일이 실행된 적이 없다면, 프로그래머가 열심히 바이너리를 배포해 놓아도 에디터에서 안드로이드같은 디바이스로 프로그램을 런칭시킬 수 없습니다( 물론 "Engine/Extras/AndroidWorks/Win64/AndroidWorks-1R1-windows.exe" 도 깔려 있어야겠죠. 여기에서는 자동화와 배포에 대한 주제를 다루지 않기 때문에 구체적인 언급은 피하겠습니다 ).


3. 소스 제어 - 의존성 파일 등록



이 과정을 통해서 생성된 파일들을 소스 제어에 등록하는 것은 취향( ? )의 문제로 보입니다. 만약 언리얼 빌드 시스템에 손을 댈 계획이라면 소스제어에 등록해야 하지만, 그렇지 않다면 등록할 필요가 없습니다.


저같은 경우에는, 언리얼 빌드 시스템에 손을 댈 계획이 없지만, 그래도 두 가지 이유 때문에 의존성 파일들을 소스 제어에 등록해 둡니다.

  • Setup.bat 는 의존성 파일들을 먼저 검색하고 없는 파일만 추가적으로 다운로드합니다. 일반적으로 외부에서 데이터를 받는 것보다는 내부에서 받는 것이 빠르기 때문에 미리 올려 놓는 것이 좋습니다. Setup.bat 를 실행했을 때 다운로드받는 양은 적어지고 필요한 작업만 수행합니다.
  • 나중에 엔진을 빌드하고 나면 의존성 파일들이 있는 디렉토리와 엔진 바이너리 파일들이 있는 디렉토리가 동일하기 때문에, 파일을 개별적으로 관리하기가 귀찮아집니다. 그냥 통째로 "Engine/Binaries" 폴더를 등록하는 것이 편합니다( 의존성 파일들이 해당 디렉토리에만 있는 의미는 아니니 헷갈리지 않길 바랍니다 ).


참고로 다른 자리에서 Setup.exe 를 실행하면 콘솔 프롬프트에 일부 파일을 덮어 쓸 것이냐는 질문을 하는데, 이 때 No 를 선택하시면 됩니다.


만약 의존성 파일을 등록해야겠다고 판단하셨다면, 새롭게 추가된 파일들을 모두 등록하시면 됩니다( 여기까지는 그냥 엔진 루트 디렉토리를 통째로 등록하면 됩니다 ).


4. GenerateProjectFiles.bat



이제 프로젝트 관련 파일들을 생성할 차례입니다. 이 파일은 프로그래머만 실행하시면 됩니다. 이 파일을 실행하게 되면 언리얼 엔진 소스 폴더를 열심히 돌면서 자동으로 프로젝트 파일을 생성합니다. 이와 관련한 도움말은 [ 자동 프로젝트 파일 생성 ] 이 있습니다.


요즘은 멀티 플랫폼에서 개발하는 시대이기 때문에, 특정 버전의 프로젝트 파일을 제공하는 것은 불합리합니다. 물론 make 파일을 사용하면 되지만, 이를 구성별로 사용자가 따로 관리하는 것은 어렵습니다. 그래서 언리얼은 이러한 과정을 모두 자동화했습니다. 안 그랬으면 UE42013.sln, UE42015.sln 같은 파일들을 만들어서 배포해야겠죠. 좀 더 나가면 UE4Editor2015.sln 같은 솔루션을 배포해야 할 수도 있었습니다.


제가 이전에 프로젝트를 진행하면서 그런 짓을 좀 해 봤는데, 정말 할 짓이 아닙니다. 파일 하나를 추가하면 솔루션이나 프로젝트를 열어서 일일이 추가해 줘야 하고, 삭제나 이동도 마찬가지입니다.


언리얼은 이 과정을 자동화시켜 사용하기 편하게 만들었습니다. 어떤 파일이 추가/삭제/이동되면 그냥 GenerateProjectFiles.bat 을 한 번 실행해 주기만 하면 됩니다. 프로젝트 관련 파일들은 Intermediate 디렉토리에 생성되며, 이러한 것들은 소스 제어에 등록될 필요가 없습니다. VS 에서 파일 구조를 filter 같은 것을 사용해서 관리하기 보다는, 실제 디렉토리를 중심으로 filter 가 생성되도록 하는 것이 더 합리적이고 파일을 찾기도 편합니다.


그런데 가끔 이 파일을 실행하면 아주 오랫 동안 아무 반응이 없을 때가 있습니다( 요즘 머신에서 15 초 넘어 가면 문제가 있습니다 ). 반응이 없어서 콘솔을 닫고 파일을 재실행하면 UnrealBuildTool.exe 가 사용중이라서 어쩌고 하면서 에러가 뜹니다. 이 경우에는 컴퓨터를 재시작하시고 Avast 같은 안티 바이러스 도구를 일시적으로 정지시키십시오. 엔진 폴더를 검사에서 배제하는 방법을 써 봤지만 소용이 없더군요. 그리고 나서 실행하시면 됩니다( 예전에는 디렉토리나 UnrealBuildTool.exe 파일을 배제하는 것만으로 됐던 것 같은데, 왜 그런지 모르겠네요. Windows 10 64bits 입니다 ). 추가 : Avast 에서 DeepScreen 기능을 아예 꺼버리니 제대로 동작합니다.


추가 : GenerateProjectFiles 를 실행하면 노란색으로 ucrt 경로 관련 경고가 출력될 경우가 있습니다. 이 경우에는 빌드도 제대로 안 되므로 반드시 해결해야 합니다. 그 방법은 [ UE4 빌드시에 ucrt 경로 충돌 문제 해결방법 ]에 정리해 뒀습니다.

 

이제 엔진 루트에 UE4.sln 이라는 솔루션 파일이 생성되어 있는 것을 확인하실 수 있습니다. 프로젝트 관련 ( 임시 ) 파일들은 "Engine/Intermediate" 디렉토리 안에 생성됩니다. 이 디렉토리는 소스제어에 등록할 필요가 없습니다. 참고로 솔루션 파일도 재생성할 수 있으므로 소스 제어에 등록할 필요가 없습니다.


5. 엔진 빌드하기



이제 엔진을 빌드해 줄 차례입니다. 언리얼 엔진을 빌드하려고 하면서 가장 어려웠던 점은 솔루션 구성을 이해하는 것입니다. 엄청나게 많은 구성들이 존재하기 때문에 머리가 엄청나게 복잡해집니다. 이와 관련한 문서는 [ 게임 프로젝트 컴파일하기 ] 와 [ 소스에서 언리얼 엔진 빌드하기 ] 등이 있습니다.



구성은 크게 5 종류의 카테고리로 나뉩니다. 각 항목은 다음과 같은 의미를 가집니다.


 항목

 설명

 Debug

 모든 프로젝트의 바이너리가 디버깅 심볼을 가지도록 합니다.

 DebugGame

 게임 프로젝트의 바이너리만 디버깅 심볼을 가지도록 합니다.

 Development

 일반적인 프로젝트에서의 Release 환경설정과 같습니다.

 Test

 Shipping 에서 콘솔, 통계, 프로우파일링을 추가한 것입니다.

 Shipping

 최상의 성능을 가진 설정입니다.


자 이제 우리는 어떠한 구성을 빌드해서 나오는 바이너리를 사용자에게 제공해야 하는걸까요? 도움말에서는 "DevelopmentEditor + Win64 + UE4" 를 빌드할 것을 권장하고 있습니다( 빌드하는데 상당히 오랜 시간이 걸리므로, 빌드를 걸어 놓고 그냥 딴 일을 하시기 바랍니다 ).


빌드할 때도 "Performing full C++ include scan (no include cache file)" 이라는 로그 이후에 오랫동안 아무런 반응이 없으면, Avast 같은 안티 바이러스 도구를 일시적으로 정지시켜야 합니다.


빌드가 끝나면 에디터를 실행하는 데 전혀 문제가 없습니다( 커스텀 혹은 인하우스 빌드는 런처를 통해서 실행할 수 없습니다, 엔진 루트의 "Engine/Binaries/Win64/UE4Editor.exe" 를 실행해야 합니다 ). 원래 에디터를 처음 실행할 때는 45% 정도에서 오래 멈춰 있으니 성급하게 끄지 말아 주세요.


그런데 에디터를 잘 가지고 놀다가 플레이( Play In Editor, PIE )를 하는 것은 상관없는데, 실행( Launch )를 하면 에러가 발생합니다.



이런 류의 에러는 앞으로 안드로이드용 개발을 하다가도 자주 만나게 되는 에러입니다. 


XXXEditor 구성을 사용하면, "UE4Editor-$(Platform)-$(Configuration).exe" 라는 바이너리를 생성합니다. 하지만 DevelopmentEditor 구성에서는 그냥 "UE4Editor.exe" 를 생성합니다.


그리고 Debug, DebugGame, Development, Test, Shipping 은 각각의 구성에 맞게 "UE4Game-$(Platform)-$(Configuration).exe" 라는 바이너리를 생성합니다. 이것이 하나의 에디터에서 다양한 플랫폼의 실행 파일을 런칭할 수 있는 비법( ? )입니다.


DevelopmentEditor 구성으로 UE4Editor.exe 를 빌드하면 기본적으로 UE4Game-Win64-Development.exe 를 런칭합니다. 다른 구성으로 빌드된 바이너리를 런칭하기 위해서는 "실행 > 프로젝트 런처" 를 클릭합니다.



그리고 나서 나오는 다이얼로그에서 오른쪽 상단의 "고급" 버튼을 클릭합니다. 그러면 각 구성과 플랫폼별로 런칭할 수 있는 구성이 나옵니다.



만약 해당 구성으로 빌드한 적이 없다면 또 "UE4Game 바이너리가 없습니다" 라는 에러 메시지를 만나게 되겠죠.


어쨌든 정리하자면 그냥 개발하는 동안에는 "DevelopmentEditor + Win64 + UE4" 와 "Development + Win64 + UE4" 구성만 빌드하면 됩니다. 만약 에디터에서 안드로이드 플랫폼에 프로젝트를 런칭하기를 원한다면 "Development + Andoroid + UE4" 를 빌드하면 됩니다.


프로젝트 런처를 통해서 실행한 프로그램을 프로세스에 연결해서 디버깅해 보고 싶다면 "Debug + Win64 + UE4" 같은 구성도 미리 빌드해 놓는 것도 좋겠죠. 하지만 이런 구성은 다른 개발자들에게 배포할 필요는 없으므로 소스 제어에는 등록할 필요가 없을 것 같습니다.


6. 소스 제어 - 바이너리 등록



"DevelopmentEditor + Win64 + UE4", "Development + Win64 + UE4", "Development + Android + UE4" 를 빌드했다면, "Engine/Binaries" 디렉토리를 통째로 소스 제어에 등록하시면 됩니다. 물론 3 번에서 의존성 파일을 등록하지 않았다면 그것을 배제하는 작업을 좀 해야겠죠. 추가 : 플러그인들도 빌드되기 때문에 Plugins 디렉토리도 같이 등록해야 합니다. 이 때 각 하위 디렉토리의 Intermediate 디렉토리에 있는 것들은 등록에서 배제해야 합니다.


추가적으로 다른 구성을 등록해야겠다고 생각하시면 그렇게 하셔도 상관은 없습니다. 아래와 같이 플랫폼별로 디렉토리가 세분화되어 있기 때문에 관리하기가 어렵지는 않을 겁니다.



7. 모듈 이해하기



언리얼에서 CPP 프로그래밍을 하다가 보면 모듈( Module )이라는 용어가 자주 등장합니다. 이와 관련해서는 [ 언리얼 아키텍처 ], [ 모듈 API 지정자 ], [ 게임플레이 모듈 ] 등의 도움말이 있지만, 그리 친절하지는 않습니다.

 

제가 대충 정리한 [ 언리얼 엔진 모듈 ] 항목을 참조하십시오. 해당 문서는 새로운 정보를 습득할 때마다 계속 업데이트 됩니다.

이러한 모듈 개념을 잘 이해하고 있어야, 나중에 다른 모듈을 사용하는데 어려움을 겪지 않게 됩니다.


8. CPP 프로젝트 만들기



이제 CPP 프로젝트를 하나 만들어 보겠습니다. 



자 이제 솔루션에는 BlankCpp 라는 프로젝트가 포함됩니다. 어떻게 포함되었을까요? 당연히 내부적으로 GenerateProjectFiles.bat 을 실행시켜서 포함시킨 것입니다


제가 언리얼을 처음 만질 때, 가장 두려웠던 것이 GenerateProjectFiles.bat 을 실행하는 것이었습니다. 혹시 뭔가 데이터가 날라가지 않을까라는 의심이 들었는데, 디렉토리에 파일이 남아 있으면 제대로 프로젝트가 생성되기 때문에 걱정하지 않으셔도 됩니다.


어쨌든 솔루션 익스플로러를 확인해 봅시다.



역시 여기에도 BlankCpp.Build.cs 라는 녀석이 생성되어 있는 것을 볼 수 있습니다. 즉 BlankCpp 모듈인 것입니다.


우리는 UE4 를 이미 빌드했기 때문에, BlankCpp 만 빌드하면 됩니다.



9. 소스제어 - CPP 프로젝트 데이터 등록



BlankCpp 프로젝트를 빌드했으면 이제 소스 제어에 올릴 차례입니다. 프로젝트 관련 파일들은 재생성하면 된다고 했기 때문에 올릴 필요가 없습니다. 그래서 아래 그림과 같이 선택된 디렉토리와 파일만 등록하면 됩니다.


그런데 다른 자리에서는 어떻게 프로젝트 파일을 생성하냐구요? "GenerateProjectFiles.bat BlankCpp.uproject -Game" 이라고 콘솔 창에서 입력하시면 됩니다. 아니면 uproject 를 열어서 에디터에서 다음과 같은 메뉴를 사용하셔도 됩니다.



또는 BlankCpp.uproject 파일의 컨텍스트 메뉴에서 생성할 수 있습니다.



어쨌든 전부다 본질은 GenerateProjectFiles.bat 에 있습니다.



10. 결론



언리얼 엔진의 빌드 시스템에 대해서 이해하면 개발이 편해지지만, 그렇지 못하면 지옥을 겪게 됩니다.


아직 제가 모르는 부분도 많고 자세히 다루지 않은 부분도 있습니다. 단지 이 문서에서는 가이드만 제시하는 것이기 때문에 다른 문서들을 참조하면서 해 보시기 바랍니다.


이 문서에서는 모듈에 대해서만 잠깐 다루고 타깃에 대해서는 다루지 않았는데, "*.Build.cs" 와 마찬가지로 "*.Target.cs" 라는 파일들이 존재합니다. 이건 dll 단위로 존재하게 됩니다. 자세한 내용은 [ 언리얼 빌드 시스템 타겟 파일 ] 도움말에서 찾아 보시기 바랍니다.


추가 : setup.bat 실행 후에 AndroidWorks 를 설치하셨다면, setup.bat 를 다시 실행해 주셔야 합니다.

 

추가 : 혹시 엔진을 커스터마이징해서 배포하는 팀( 혹은 사람 )과 엔진을 받아서 사용하는 팀이 구분되어 있고 서로의 영역을 건드리지 않는다면, [ 빌드 그래프 ] 와 [ Installed Build 참고서 ] 를 참고해서 배포하시기 바랍니다. 그냥 빌드해서 서밋하시면 코드가 변경되었기 때문에 엔진 코드를 수정하면 게임 칸텐츠를 작업하는 측에서 엔진까지 빌드해야 하는 경우가 생깁니다.

언리얼 엔진의 [ 데이터 주도형 게임플레이 요소 ] 항목을 따라 하다가 보면 문제가 발생합니다. 


UE4 에서는 UTF-8 포맷으로 CSV 를 읽고 있기 때문에, 일반적인 방식으로 XLSX 에서 CSV 를 뽑게 되면 한글같은 것은 다 깨져서 나옵니다.


이를 해결하기 위한 일반적인 방법은 다음과 같습니다.


  1. XLS 를 편집.
  2. CSV 로 저장( 이 과정에서 메시지 박스가 자꾸 떠서 불편함 ).
  3. CSV 를 메모장에서 열어 UTF-8 포맷으로 재저장.
  4. 다시 편집할 때는 1 ~ 3 을 반복.


이런 과정은 너무 불편한 데다가 실수로 CSV 만 손대면 XLS 와 내용이 틀려져서 관리가 어렵습니다.


그래서 이를 자동화하는 방법을 고민했고, 이런 저런 자료들을 참고하여 매크로를 만들었습니다: XLSM( 매크로 XLS )사용해서 CSV 를 익스포트할 수 있도록 했습니다.


앞으로 UE4 에서 사용할 엑셀 원본 파일은 아래의 파일을 기반으로 만들면 됩니다.


XlsToCsv.xlsm


여기에서 사용하는 VBAScript 는 다음과 같습니다.



그런데 "Export" 라는 sheet 에만 연결을 해 두었기 때문에, 다른 sheet 에서 작업을 하려면 따로 스크립트를 연결해야 합니다.




무기를 위해서 UStaticMeshComponent 를 상속하는 UStaticWeaponComponent 를 만들었다고 가정합시다.



다음으로 나서 내가 정의한 커스텀 캐릭터의 멤버로 넣습니다.



다음으로 생성자에서 Weapon 컴포넌트를 생성해 줍니다.



그리고 나서 ACustomCharacter 를 가지고 플루프린트 클래스를 만듭니다. 그 다음 컴포넌트 리스트에서 Weapon 컴포넌트를 선택하면 Details 패널에 다음과 같이 나옵니다.


그림1. EditAnywhere 를 지정했을 때 Details 패널.


뭔가 평소에 보던 것과는 다릅니다. 이는 EditAnywhere 라는 속성 지정자 때문입니다. 보통 VisibleAnywhere 와 EditAnywhere 를 사용하는데, 이는 서로 배타적입니다. 만약 이것을 VisibleAnywhere 로 변경한다면 다음과 같은 Details 패널을 볼 수 있습니다.


그림2. VisibleAnywhere 를 지정했을 때 Details 패널.


그렇다면 EditAnywhere 와 VisibleAnywhere 의 차이는 무엇일까요? 언리얼 엔진 공식 문서에서는 다음과 같이 설명합니다.


EditAnywhere


이 속성이 아키타입( archetype )이나 인스턴스의 속성창에서 편집될 수 있음을 의미합니다.


출처 : https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/EditAnywhere/index.html


VisibleAnywhere


이 속성이 속성창에서 보이지만, 편집할 수 없음을 의미합니다. 이 연산은 Edit* 지정자들과 호환되지 않습니다.


출처 : https://docs.unrealengine.com/latest/INT/Programming/UnrealArchitecture/Reference/Properties/Specifiers/VisibleAnywhere/index.html


일단 편집이 된다와 안 된다가 가장 큰 차이인 것 같지만, 실제로는 둘 다 편집이 가능합니다. 설명이 참 이상한 것 같습니다.


추가 : 언리얼 까페의 나가놀자 님에 따르면 두 옵션의 차이는 다음과 같다고 합니다.


Edit은 포인터 자체를 수정하겠다는 의미가 됩니다.

반면 Visible은 포인터의 멤버를 노출하겠다는 의미가 됩니다.


출처: http://cafe.naver.com/unrealenginekr/7259


어쨌든 UI 상에 있어 핵심적인 차이는 아키타입이나 인스턴스 속성창에서 편집될 수 있다는 것입니다. 언리얼에서 아키타입은 다른 엔진들의 프리팹 정도라고 생각하시면 될 것 같습니다. 어쨌든 중요한 것은 인스턴스별로 편집이 가능하다는 것입니다.


예를 들어 EditAnywhere 를 지정하고 만든 클래스의 인스턴스를 생성하면 그 인스턴스의 Details 패널에서 다음과 같은 항목을 볼 수 있습니다. VisibleAnywhere 를 지정하면 나오지 않습니다.



그림3. EditAnywhere 를 지정했을 때 인스턴스의 Details 패널.


정리하자면, EditAnywhere 는 속성을 인스턴스별 속성으로 만들 때 필요하며, VisibleAnywhere 는 속성을 블루프린트 자체의 속성으로만 사용할 때 필요합니다. EditAnywhere 를 지정했을 때 그림1 처럼 접혀 있는 이유는 인스턴스의 Details 패널이 너무 위아래로 길어지지 않도록 하기 위함으로 보입니다( 그래도 저같으면 블루프린트 안에서는 펼쳐져 있도록 구현했을 것 같은데요 ㅡㅡ;; ).


인스턴스별 속성이 뭐냐고 질문하는 분도 계실 겁니다. 그냥 인스턴스별로 속성을 재정의( overriding )할 수 있다는 의미입니다. 만약 EditAnywhere 를 지정한 경우라면 블루프린트 클래스를 여러 개 만들지 않고 하나만 만든 다음에 씬에 배치한 후에 속성을 편집할 수 있겠죠. 만약 VisibleAnywhere 를 지정한 경우라면 생성할 인스턴스 개수마다 블루프린트 클래스를 만들어야만 합니다.


물론 프로그래밍이나 스크립팅을 통해서 이를 제어한다면 상관없지만, 아티스트나 디자이너가 뭔가 하나씩 배치하고 속성을 바꾸기를 원한다면 EditAnywhere 를 사용하는 것을 고려해야 합니다.

언리얼 엔진을 커스터마이징하다가 보면 액터의 컴포넌트를 직접 정의해야 하는 경우가 있습니다. 예를 들어 무기 컴포넌트를 만든다고 합시다.


이 경우에 스태틱 메쉬 애셋을 사용할 것이므로 UStaticMeshComonent 를 상속하는 UStaticWeaponComponent 를 생성합니다. 혹시 채찍같은 것을 만들면 USkeletalMeshComponent 를 상속하는 USkeletalWeaponComonent 를 만들어야겠죠.



그런데 이러한 컴포넌트를 만들고 나서 액터에 추가하려고 하면 "Add Component" 드롭다운 메뉴에 내가 만든 컴포넌트가 리스팅되지 않습니다.




이는 UStaticWeaponComponent 을 생성할 수 있도록 지정하지 않았기 때문입니다. 아래와 같이 클래스 지정자를 설정하면 됩니다. 



이제 "Static Weapon" 항목이 추가되어 있는 것을 확인할 수 있습니다. 참고로 ClassGroup 이라는 것은 이 컴포넌트의 카테고리를 의미합니다.




언리얼 엔진을 빌드할 때 "Ctrl + Break" 를 누르면 그 다음부터 빌드가 진행되지 않는 경우가 있습니다. 


이 때 에디터나 VS 를 다시 닫았다가 열어도 빌드를 진행하지 못하고 아무리 기다려도 소용이 없습니다. 심지어 몇 시간을 기다려도 빌드는 끝나지 않습니다. 실제로 졸려서 자고 왔는데도 그 상태더군요.


이것의 원인은 UnrealBuildTool.exe 프로세스입니다. 이 상태가 되면 빌드를 누를 때마다 프로세스가 계속 누적이 됩니다.


그냥 UnrealBuildTool.exe 프로세스를 종료시켜 주시면 됩니다. 만약 종료가 안 된다면 VS 를 끄고 종료시키면 됩니다.


저같은 경우에는 작업관리자를 열었다가 닫는 것이 귀찮아서 바탕화면에 "KillUEBuildProc.bat" 파일을 만들어 놓고 종료시킵니다.



다른 게임들의 전투 시스템을 어떻게 구현하는지 궁금해서 제가 좋아했던 와우의 전투 시스템에 대해서 번역해 보았습니다. 아래 첨부된 PDF 파일을 보시면 됩니다.



WOW_Battale.pdf



목차


1. 전투 이벤트 5

    1.1. 일반 전투 이벤트 5

        1.1.1. 평타( Hit ) 5

        1.1.2. 치명타( Critical Hit ) 5

    1.2. 난투 관련( melee-specific ) 전투 이벤트 5

        1.2.1. 실패( Miss ) 5

        1.2.2. 회피( Dodge ) 6

        1.2.3. 무기막기( Parry ) 6

        1.2.4. 방패막기( Block ) 6

    1.3. 주문 관련 전투 이벤트 7

        1.3.1. 저항( Resist ) 7

    1.4. NPC 관련 전투 이벤트 7

        1.4.1. 강타( Crushing Blow ) 7

        1.4.2. 빗맞음( Glancing Blow ) 7


2. 히트 테이블 8

    2.1. 히트 테이블 이해하기: 기초 8

    2.2. 한-주사위 혹은 두-주사위? 치명타 9

    2.3. 결과 배제하기 11

    2.4. 역학 세부사항 12

        2.4.1. 공격 순서 12

    2.5. 특수 규칙 12

        2.5.1. 몹 상호작용 12

        2.5.2. 후방 공격 12

        2.5.3. 사냥꾼과 자동사격 12


3. 데미지 12

    3.1. 물리 데미지 12

    3.2. 주문 데미지 및 힐링 13


4. 위협( Threat ) 14

    4.1. 증오 리스트 14

    4.2. 위협 생성 15

    4.3. 타깃 변경 16

        4.3.1. 끈끈한( Sticky ) 어그로 16

        4.3.2. 도발( Taunting ) 17

        4.3.3. 어그로 상실 17

    4.4. 위협 조절하기 17

    4.5. 위협과 탱킹 19

    4.6. 위협과 치유 19

    4.7. 위협과 DPS 19

    4.8. 비표준 증오 구조. 19


5. DPS 20

    5.1. 무기에서 DPS 20

    5.2. 캐릭터에 대한 DPS 21

    5.3. 캐릭터 특성으로서의 DPS 21

    5.4. 역할로서의 DPS 21

    5.5. 동사로서의 DPS 21


6. 능력치( Attributes ) 21



너무 베끼려고 하지 말고 느낌( 양감 )을 살리려고 노력하라고 해서 다시 시도해 봄.

 

여전히 맘에 안 들지만 어제보다는 훨씬 빠른 시간에 더 나은 결과가 나온듯...

 

 

 

'ETC > Digital Painting' 카테고리의 다른 글

미사일  (0) 2016.04.07
물고기  (0) 2015.10.14
인간 뼈대  (0) 2014.09.09
고양이 뼈대와 몸  (0) 2014.09.03
고양이 근육 연습  (0) 2014.08.31
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18

사진 보고 그려 봤는데, 그리드를 써야 대충이라도 비율을 맞출 수 있다. 눈이 삐꾸임 ... 사진은 날렵한 깡패 고기같은데, 내가 그린건 뚱보임.


색감은 정말 맞추기 어려운 듯. 컬러 픽커를 안 쓰고 그냥 대충 맞춰 봤는데, 절대 안 맞음. 연습을 많이 안 해 봐서 그럴 것이라 생각하기는 하지만... 눈이 맛이 간 게 아닐까 의심이 들기도... 


여러 색이 섞여 있는 팔레트에서 같은 색을 고르는 게 참 어려운 것 같다.


디테일 표현은 어떻게 하는 건지 잘 모르겠다. 빡세게 그리든가, 적절한 브러쉬를 쓰든가 해야 할 듯.




'ETC > Digital Painting' 카테고리의 다른 글

미사일  (0) 2016.04.07
물고기2  (0) 2015.10.15
인간 뼈대  (0) 2014.09.09
고양이 뼈대와 몸  (0) 2014.09.03
고양이 근육 연습  (0) 2014.08.31
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18

파일 :

 

Curve.pdf

 

목차 :

 

목차 ............................................................................................................................................................................................... 2

 

들어 가며 ........................................................................................................................................................................................ 4


1. Combination ...................................................................................................................................................................... 6

1.1. Linear combination ...................................................................................................................................... 6

1.2. Linear independence .................................................................................................................................. 7

1.3. Cartesian coordinate system .............................................................................................................. 8
1.4. Convex combination .................................................................................................................................... 9


2. Polynomial & interpolation .............................................................................................................................. 13


2.1. Linear interpolation .................................................................................................................................. 16
2.2. Polynomial interpolation ..................................................................................................................... 17
2.3. Method of undetermined coefficients ...................................................................................... 18
2.4. Lagrange interpolation .......................................................................................................................... 19
2.5. Divided difference ...................................................................................................................................... 21
2.5.1. Linear interpolation .................................................................................................................... 21
2.5.2. Quadratic interpolation ........................................................................................................... 22
2.5.3. Divided difference & General intepolation............................................................ 23
2.6. Newton interpolation .............................................................................................................................. 25
2.7. Problem of polynomial interpolation ........................................................................................ 26
2.8. Spline interpolation .................................................................................................................................. 28
2.8.1. Cubic spline interpolation ..................................................................................................... 29
2.8.2. Solving natural cubic spline ............................................................................................... 31


3. Parametric equation & continuity ............................................................................................................. 33


3.1. Parametric equation ................................................................................................................................ 34
3.2. Parametric continuity ............................................................................................................................. 37
3.3.Geometric Continuity ................................................................................................................................ 40


4. Bé zier curve ................................................................................................................................................................... 42


4.1. Linear Bé zier curve ................................................................................................................................... 43
4.2. Quadratic Bé zier curve .......................................................................................................................... 43
4.3. Cubic Bé zier curve ..................................................................................................................................... 45
4.4. Composite Bezier curve ........................................................................................................................ 48


5. Spline Curve ................................................................................................................................................................... 50


5.1. Linear spline curve .................................................................................................................................... 50
5.2. Quadratic spline curves ........................................................................................................................ 51
5.3. Spline curves of higher degrees ................................................................................................... 53


6. Hermite spline .............................................................................................................................................................. 56


6.1. Finite difference ........................................................................................................................................... 58
6.2. Cardinal spline ............................................................................................................................................... 58
6.3. Catmul-Rom spline .................................................................................................................................... 58
6.4. Kochanek-Bartels spline ...................................................................................................................... 59
6.5. Monotone cubic interpolation ......................................................................................................... 60


7. B-Spline( Basis spline ) ....................................................................................................................................... 61


8. Nonuniform rational B-Spline( NURBS ) ............................................................................................. 65


8.1. Nonuniform B-spline ................................................................................................................................ 65
8.1.1. Uniform knot vector ................................................................................................................... 65
8.1.2. Open uniform knot vector .................................................................................................... 66
8.1.3. Non-uniform knot vector ....................................................................................................... 67
8.2. Rational curve ................................................................................................................................................ 68
8.3. Non-Uniform Rational B-Spline ..................................................................................................... 71
8.4. Construction of the basis functions ........................................................................................... 72
8.5. General form of a NURBS curve .................................................................................................... 76
8.6. General form of a NURBS surface ............................................................................................... 76


9. Subdivision Surface ................................................................................................................................................ 76

UML basics Part II: The activity diagram



원문링크 : https://www.ibm.com/developerworks/rational/library/content/RationalEdge/sep03/f_umlbasics_db.pdf


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.




2003 년에, Rational Edge 는 IBM 글로벌 서비스의 도날드 벨( Donald Bell ) 이 작성한 새로운 아티클 시리즈를 소개했는데, 그것은 UML Basics 라고 합니다. 이 시리즈의 목적은 독자들이 UML 의 대부분을 구성하는 주요 다이어그램에 익숙해지도록 돕는 것입니다. 파트 1 은 이 다이어그램들에 대한 일반적인 개관을 제공했습니다; 이번 달에는, 활동( activity ) 다이어글매에 대한 세부 사항에 대해서 다루는데, 이 다이어그램은 완전한 UML v1.4 표기 집합을 포함하고 있습니다.



활동 다이어그램의 목적



활동 다이어그램의 목적은 큰 활동의 일부인 액션들의 절차적 흐름을 모델링하는 데 있습니다. 유스 케이스가 제출된 프로젝트에서, 활동 다이어그램들은 특정 유스 케이스를 좀 더 세부적인 레벨에서 모델링할 수 있습니다. 그러나 활동 다이어그램들은, 콘서트 티킷을 구입하거나 단과 수업을 등록하는 것과 같은, 비즈니스-레벨 기능들을 모델링하기 위해 유스 케이스들과는 독립적으로 사용될 수 있습니다. 활동 다이어그램들은, 티킷 예약 데이터 마트가 공동 세일즈 시스템의 데이터 웨어하우스를 덧붙이는 방식과 같은, 시스템-레벨 기능들을 모델링하는데 사용될 수도 있습니다.


그것은 절차적인 흐름을 모델링하기 때문에, 활동 다이어그램은 실행을 위한 액션 시퀀스와 그러한 액션들을 유발하거나 보호하는 상태들에 초점을 맞춥니다. 또한 활동 다이어그램은 활동의 내부 액션들에만 초점을 맞춥니다. 그것들의 처리 흐름에서 활동을 호출하는 액션들이나 특정 이벤트와 관련해 활동을 유발하는 액션들에는 관심을 두지 않습니다( 4월 13일 12:30 이며, 그룹의 섬머 투어를 위한 그린 데이 티킷들을 이제 판매한다 ).


UML 시퀀스 다이어그램들이 활동 다이어그램과 동일한 정보를 보여 주기는 하지만, 나는 개인적으로 활동 다이어그램들이 비즈니스-레벨 기능들을 모델링하는 것에 있어 최고라는 것을 발견했습니다. 왜냐하면, 활동 다이어그램은 활동에 있어서의 모든 잠재적인 시퀀스 흐름들을 보여 주기 때문입니다. 반면에 시퀀스 다이어그램은 보통 활동에 대한 하나의 흐름만을 보여 줍니다. 또한, 비즈니스 매니저와 비즈니스 인사 과정( process personnel )은 시퀀스 다이어그램들 보다는 활동 다이어그램을 선호하는 것처럼 보입니다 -- 활동 다이어그램은 보기에 덜 "기술적"이며, 그래서 비즈니스를 하는 사람들에게 겁을 덜 줍니다.게다가, 비즈니스 매니저들은 흐름 다이어그램들을 보는 것에 익숙해서, 활동 다이어그램의 "외형"에 친숙합니다.



표기( notation )



활동 다이어그램의 표기는 상태차트 다이어그램과 매우 유사합니다. 사실, UML 명세에 따르면, 활동 다이어그램은 상태차트 다이어그램의 변종입니다. 그래서 만약 당신이 상태차트 다이어그램에 익숙하다면, 활동 다이어그램 표기법을 이해하는데 한 다리 걸치게 된 것이며, 아래에서의 많은 논의들이 당신에게는 리뷰가 될 것입니다.



기본



먼저, 활동 다이어그램에서 액션 요소를 살펴 봅시다. 이것의 공식 UML 이름은 액션 상태( action state )입니다. 내 생각에는 사람들이 그것을 액션 상태라고 부르는 경우는 거의 없는 것 같습니다; 보통 액션이나 활동이라고 부릅니다. 이 기사에서, 나는 그것을 항상 액션이라고 부르도록 하겠습니다. 그리고 활동이라는 개념은 활동 다이어그램에 의해서 모델링되고 있는 전체 작업을 부를 때만 사용할 것입니다. 이러한 구분은 내 설명을 더 이해하기 쉽게 만들어 줄 것입니다.


활동 다이어그램에서의 액션은 "캡슐" 모양에 의해 표시됩니다 -- 왼쪽과 오른쪽 끝에 반쪽 원들을 가지고 있는 사각형 개체입니다( Figure 1 참조 ). 내부의 텍스트는 액션을 가리킵니다( 예를 들어 "사용자가 티킷 사무소에 전화를 겁니다" 혹은 "등록 사무소가 문을 엽니다" ).


Figure 1: 활동 다이어그램의 일부인 샘플 액션.


활동 다이어그램들은 액션의 시퀀스를 보여 주므로, 그것들은 반드시 시퀀스의 시작 지점을 표시해야 합니다. 활동 다이어그램 시작 지점의 공식 UML 이름은 초기 상태( initial state )입니다. 그리고 그것은 액션 시퀀스를 읽기 시작할 위치입니다. 초기 상태는 채워진 원으로 그려지는데, 활동의 액션 시퀀스에서 처음 액션과 연결하는 전이 라인( transition line, 화살표 )를 가지고 있습니다. Figure 2 는 활동 다이어그램의 초기 상태가 어떻게 생겼는지 보여 줍니다. UML 명세는 활동 다이어그램에서 초기 상태의 위치를 지정하지는 않습니다만, 보통은 당신의 다이어그램의 좌상단 코너에 있는 첫 번째 액션에 배치되는 것이 가장 쉽습니다.


Figure 2: 초기 상태는 활동 다이어그램의 액션 시퀀스를 위한 시작 지점을 명확하게 보여 줍니다.


활동 다이어그램에는 하나만의 초기 상태만 존재할 수 있으며, 초기 상태와 연결되는 하나만의 전이 라인만 존재할 수 있다는 것을 기억하는 것이 중요합니다. 활동 다이어그램이 하나만의 초기 상태를 가질 수 있다는 것이 명확해 보임에도 불구하고, 애매한 상황들이 있습니다 -- 이름하여, 비동기 액션 시퀀스의 시작. 이 상황들은 활동 다이어그램에서 새로운 초기 상태가 표시되어야 함을 암시할 수도 있습니다. Figure 3 은 잘못된 활동 다이어그램의 예를 보여 줍니다. 왜냐하면 그것의 초기 상태가 두 개의 액션들을 가리키는 두 개의 전이 라인을 가지기 때문입니다.


Figure 3: 활동 다이어그램에서 초기 상태의 잘못된 렌더링. 초기 상태는 단지 하나의 액션만을 지목할 수 있습니다.


화살표가 가리키는 방향과 함께, 활동 다이어그램의 전이 라인들은 모델링된 활동에서의 액션의 연속된 흐름을 보여 줍니다. 화살표는 항상 활동 다이어그램 시퀀스에서의 다음 액션을 가리킵니다. Figure 4 는 완전한 활동 다이어그램을 보여 주는데, 고객이 콘서트 티킷을 예약하는 방법을 모델링합니다.


Figure 4: 이해하기 쉬운 액션 시퀀스를 만드는 완전한 활동 다이어그램.


Figure 4 의 샘플 활동 다이어그램은 "콘서트 티킷 예약하기" 활동을 기술하는데, 다음과 같은 순서로 액션을 실행합니다:

  1. 고객이 티킷 사무소에 전화를 겁니다.
  2. 티킷 판매 대리인( Ticket rep )은 그 사람이 어떤 이벤트를 위한 티킷을 원하는지 묻습니다.
  3. 고객이 대리인에게 선택한 이벤트를 말해 줍니다.
  4. 대리인이 이용 가능한 좌석과 가격을 고객에게 말해 줍니다.
  5. 고객이 선택한 좌석을 대리인에게 말해 줍니다.
  6. 대리인이 좌석을 예약합니다.
  7. 대리인이 신용 카드와 청구 주소에 대해 묻습니다.
  8. 고객이 요구된 정보를 제공합니다.
  9. 대리인이 신용 카드를 결제합니다.
  10. 대리인이 티킷을 우편으로 보냅니다.


위의 액션 순서는 다이어그램에서 명확합니다. 왜냐하면 다이어그램은 초기 상태( 시작 지점 )를 보여 주고, 그 지점으로 부터 사람들은 전이 라인을 따라서 그것들이 연결된 활동들의 액션을 따라갈 수 있기 때문입니다.


활동의 흐름은 시퀀스에서 마지막 액션의 전이 라인이 "최종 상태" 심볼과 연결될 때 끝납니다. 그 심불은 과녁모양입니다( 채워진 원을 둘러싼 원 ). Figure 4 에서 보이듯이, "Ticket Rep Mails Tickets" 액션은 최종 상태 심볼과 연결되며, 그것은 활동의 액션 시퀀스가 끝에 도달했음을 지시합니다. 모든 활동 다이어그램은 적어도 하나의 최종 상태 심볼을 가집니다; 그렇지 않으면, 독자들은 어디에서 액션 시퀀스가 끝나는지 불명확하게 느낄 것이며, 혹은 활동 다이어그램이 여전히 작동중이라고 간주하게 될 것입니다.


활동 다이어그램이 여러 개의 최종 상태를 가지는 것이 가능합니다. 활동 다이어그램에서 단 하나만 존재해야 하는 초기 상태 심볼과는 다르게, 최종 상태 심볼은 로직 내의 많은 가지들 중의 하나의 끝을 표현할 수 있습니다 -- 다시 말해, 활동 다이어그램은 여러 가지 방식으로 끝날 수 있습니다.



기본의 너머에



우리는 활동 다이어그램의 기본 표현 요소들에 대해 다뤄왔지만, 이 유형의 다이어그램에 배치될 수 있는 더 많은 표현 요소들이 여전히 존재합니다. Figure 4 의 다이어그램이 기술적으로는 완전하지만, Figure 4 에서 모델링된 활동은 매우 단순합니다. 보통, 실제 소프트웨어 개발 프로젝트에서 모델링되는 활동들은 어떤 액션들을 취할지를 제어하는 결정 지점( decision point )들을 포함합니다. 그리고 가끔은 활동들이 병행적인 액션들을 가집니다.



결정 지점( decision points )


일반적으로, 결정은 활동 전반에서 필요한데, 특정한 이전 액션의 출력에 의존합니다. 그러한 경우에 활동 다이어그램을 생성하면서, 당신은 두 개 이상의 서로 다른 액션 시퀀스를 모델링할 필요가 있을 것입니다. 예를 들어, 중국 배달 음심을 주문할 때, 나는 중국 음식 배달원에게 전화를 겁니다. 그리고 내 전화 번호를 줍니다. 그리고 내가 이전에 음식을 주문한 적이 있다면 그의 컴퓨터가 자동적으로 내 주소를 출력해 줄 것입니다. 하지만 내가 처음 전화를 건 새로운 고객이라면, 그는 내 주문을 받기 전에 내 주소를 획득해야 할 것입니다.


UML 명세는 결정을 모델링하기 위해, 아래와 같이 두 가지 방식을 제공합니다.


첫 번째 방식은 액션에서 나오는 단일 전이 라인을 표시하고, 그것을 결정 지점과 연결하는 것입니다. UML 명세는 결정 지점을 결정( decision )이라고 부릅니다. 그리고 그것은 활동 다이어그램에서 다이아몬드 모양으로 그려집니다. 결정은 적어도 두개의 서로 다른 출력을 가질 것이므로, 결정 심볼은 서로 다른 액션들과 연결되는 다중 전이 라인들을 가질 것입니다. Figure 5 는 결정을 포함하는 샘플 활동 다이어그램의 일부를 보여 줍니다.


Figure 5: 결정 지점은 액션 시퀀스 내에서 생성되어야만 하는 선택을 모델링합니다.


Figure 5 에서 보이듯이, 결정 지점에 포함된 각 전이 라인은 반드시 "가드 조건( guard condition )"을 지시하기 위해서 위와 같은 텍스트에 의해서 레이블링되어야만 합니다. 이는 보통 guards 라고 발음됩니다.


가드 조건 텍스트는 항상 대괄호 내에 배치됩니다 -- 예를 들어 [ 가드 조건 텍스트 ] 입니다. 가드 조건은 전이 조건이 다음 액션으로 언제 이동할 지를 명시적으로 말해 줍니다. Figure 5 의 결정 지점에 따르면, 바텐더( 사용자 )는 단지 고객이 술을 주문할 때 "사용자가 적어도 21 살인지 확인한다" 만을 필요로 합니다. 만약 고객 주문이 다른 종류의 음료를 주문하면( "else" 조건 ), 바텐더는 그냥 고객에게 드링크를 제공하기만 하면 됩니다. 활동 다이어그램에서 [else] 가드는 보통 "만약 다른 가드된 전이 라인들이 실제 조건과 일치하는 것이 없을 때" 를 의미하기 위해서 사용되며, [else] 전이 라인을 따라 가게 됩니다.



병합 지점( merge points )


Figure 6 의 "고객 나이 >= 21" 에서 볼 수 있듯이, 가끔은 한 조건 경로로부터의 절차적 흐름이 다른 조건 경로로 다시 연결됩니다. 이러한 경우에, 우리는 두 개 이상의 액션 경로들을 다중 경로들이 그것을 가리킬 때 사용했던 것과 동일한 다이아몬드 아이콘을 사용해 연결합니다. 그렇지만 그것으로 부터 나오는 전이 라인은 하나 뿐입니다. 이는 결정 지점을 가리킨다기 보다는, 병합( merge )을 가리킵니다.


Figure 6 은 Figure 5 와 같은 결정을 보여 주지만, Figure 6 은 그 활동 다이어그램을 확장합니다. 고객의 나이에 대한 검사를 수행하는 것은 사용자를 두 번째 결정으로 유도합니다; 만약 고객이 21 살 보다 어리다면, 바텐더는 고객에게 다른 비알콜 음료를 주문하라고 말해야만 하며, 그것은 "Customer Orders Drink" 액션으로 시퀀스를 되돌립니다. 그러나 만약 고객이 21 살 이상이라면, 우리 액션 시퀀스는 비알콜 드링크를 주문했을 때 바텐더가 따라야 하는 것과 동일한 액션을 취하게 됩니다: "Get Drink For Customer".


Figure 6: 부분 활동 다이어그램, 

두 개의 결정 지점( "Drink is alcoholic" 과 "Customer's age < 21" )과 하나의 병합 지점( "else" 와 "Customer's age >= 21" )을 보여 줍니다.



대안적인 접근


결정을 모델링하는 두 번째 접근법은 Figure 7 에서처럼 액션의 결과로 다중의 전이 라인을 가지는 것입니다. 만약 이 기법이 사용되면, 액션으로부터 나오는 전이 라인은 첫 번째 접근법의 결정처럼( 예를 들어 다이아몬드 심볼 ) 반드시 가드 레이블을 그 위에 가져야만 합니다. 결정 심볼에 적용되는 모든 규칙이 액션 외부에서 모델링되는 결정들에 적용됩니다. 갱니적으로, 나는 이 접근법을 추천하지 않습니다. 왜냐하면 나는 결정에 대한 가시적인 대기열을 선호하기 때문입니다. 그렇기는 하지만 UML 1.4 명세는 Figure 7 에서 볼 수 있듯이 이 접근법을 허용하며, 완결성을 높이기 위해서 여기에서 그것을 언급했습니다.


Figure 7: 나는 이 접근법을 선호하지는 않지만, UML 1.4 명세는 가드 조건들을 사용해 결정을 액션처럼 모델링하는 것을 허용합니다.


만약 당신이 이 접근법을 사용한다면, 반드시 다중 액션 시퀀스를 단일 시퀀스로 병합해야만 합니다. 이를 위해서, 당신은 특정 액션 시퀀스 내의 마지막 전이 라인을 시퀀스가 다시 시작되는 곳에 연결합니다. Figure 7 에서, "Tell Customer to order a non-alcoholic drink" 로부터의 전이라인이 "Customer orders drink" 로 되돌아 가는 것을 보여 줍니다.


Figure 8: 결정을 모델링하는 두 번째 접근법.



비동기 액션( asynchronous actions )들을 위한 동기화 상태( synch states )


활동을 모델링할 때, 가끔 병행적으로나 비동기적으로 수행될 수 있는 액션 시퀀스들을 보여 줘야 할 필요가 있습니다. 특별한 표기 요소가 병행 액션 시퀀스들을 보여 줄 수 있도록 합니다. 공식적인 이름으로는 동기화 상태( synch states )인데, 그것이 활동 흐름의 동기화 상태를 가리키기 때문입니다. 동기화 상태는 실행 스레드의 분기( forking )와 결합( joining )을 허용합니다.명확함을 위해 언급하자면, 액션을 두 개 이상의 스레드로 분기시키는 동기화 상태는 ( 비동기 액션들인 ) 비동기화( de-synchronizing )를 표현하며, 액션들을 다시 결합하는 동기화 상태는 동기화된 흐름으로의 복귀를 표현합니다. 동기화 상태는 굵은 채워진 라인으로 그려지는데, 전이 라인이 ( 보통 ) 그것의 왼쪽으로 들어 오며, ( 보통 ) 오른쪽으로 나갑니다. 액션 시퀀스를 다중 스레드로 분기시키는 동기화 상태를 그리기 위해서는, 먼저 병행 시퀀스에 앞서 액션으로부터의 전이 라인을 동기화 상태에 연결합니다. 그리고 나서 두 개의 전이 라인을 동기화 상태로부터 뽑아서, 각각을 자신의 액션에 연결합니다. Figure 9 에서 실행의 분기를 보여주는 활동 다이어그램의 일부분입니다.


Figure 9: 굵은 채워진 라인은 동기화 상태를 가리키는데,

두 개 이상의 액션 시퀀스가 병행적으로 처리되는 것을 허용합니다.


Figure 9 에서 보여준 예제에서, "Receive Order" 액션이 완료된 후에, 두 개의 스레드들이 병행적으로 수행됩니다. 이는 시스템이 "Verify Ordered Products Are In Stock" 액션과 "Verify Customer Has Available Credit" 액션을 동시에 처리할 수 있도록 해 줍니다.


실행을 다중 스레드로 분기시킬 때, 보통 나중의 처리를 위해서 특정 지점에서 그것들을 다시 합쳐야 합니다. 그러므로, 동기화 상태 요소는 다중 스레드들이 단일 스레드로 결합되는 것을 표현하는데 사용되기도 합니다. Figure 10 은 두 개의 스레드가 하나의 스레드로 결합되는 활동 다이어그램의 일부분을 보여 줍니다.


Figure 10: 병행 액션 시퀀스가 종료될 때, 동기화 상태( 굵은 라인 )이 사용되는데,

이는 다중 스레드가 다시 단일 스레드로 결합되는 것을 지시합니다.


Figure 10 에서, "Verify Ordered Products Are In Stock" 액션과 "Verify Customer Has Available Credit" 액션은 Figure 9 에서 보여 준 것이며, 이는 처리가 완료된 것입니다. 그리고 "Accept Order" 액션이 처리됩니다. 단일 전이 라인이 동기화 상태로부터 나오는 것은 이제 단지 하나만의 실행 스레드가 존재한다는 것을 의미한다는 것을 기억하십시오.


동기화 상태는 활동에서의 전체적인 액션 실행에서 동기화 지점으로서 사용될 수도 있습니다. 이 경우에, 그것은 개별 스레드들이 그 이후의 실행이 진행되기 이전에 강제로 결합되는 방식을 모델링합니다. Figure 11 은 동기화 지점으로서 동기화 상태를 사용하는 활동 다이어그램의 일부분을 보여 줍니다.


Figure 11: 동기화 지점으로서 동기화 상태를 사용함.


추상적이기는 하지만, 나는 Figure 11 의 다이어그램은 동기화 상태가 동기화 지점으로 사용되는 방식을 쉽게 이해하게 해 주는 방식을 제공한다고 생각합니다. 이를 이해하기 위해서, 활동 시퀀스를 여기에서 명확하게 해 봅시다. 활동은 하나의 스레드에서 모든 액션들을 시작합니다. 그리고 "Main Thread Action XXX" 액션이 실행될 때, 활동은 세 개의 스레드로 분리되며 병행적으로 실행됩니다. 첫 번째 스레드에서, "Thread 1 Action 1" 이 실행되고, 그리고 나서 "Thread 1 Action 2" 이 실행됩니다. 이 첫 번째 스레드가 실행되는 것과 동시에, 두 번째 스레드와 세번째 스레드가 실행됩니다 -- 각각 "Thread 2 Action 1" 과 "Thread 3 Action 1" 입니다. 두 번째 동기화 상태를 가짐으로써( Figure 11 의 오른쪽에 있는 것, 역주 : 아래쪽인듯 ), 우리는 더 진행하기 전에 "Thread 1 Action 2", "Thread 2 Action 1", "Thread 3 Action 1" 이 완료될 때까지 기다립니다. 모든 이전 액션들이 수행되면, 오른쪽 동기화 상태는 이전 세 스레드들을 동기화합니다. 그리고 나서 두 개의 스레드가 분기되고, 이 스레드들에서 두 액션들이 병행적으로 실행됩니다.


이제, 이 예제에서 가장 중요한 부분이 무엇인지가 나옵니다. 다중 스레드가 실행중일 때, 한 스레드의 액션들은 병행 스레드들에서 실행중인 액션들에 영향을 주어서는 안 됩니다. Figure 11 에서, "Thread 1 Action 1" 액션은 아마도 빨리 수행될 것이고, "Thread 1 Action 2" 액션은 "Thread 2 Action 1" 이 완료되기 전에 처리를 시작할 것입니다. 다른 병행 스레드들을 위해 스레드들을 대기하도록 만드는 유일한 것은 동기화 상태이며, Figure 11 에서 보여 준 것처럼 배치되어 있습니다.


위의 모든 예제에서, 동기화 상태들은 굵은 수직 라인으로 그려집니다; 하지만 UML 명세는 이 라인들이 한 방향이나 다른 방향을 향해야 한다고 요구하지는 않습니다. 동기화 상태는 수평 굵은 라인이거나 심지어는 대각선의 굵은 라인일 수도 있습니다. 그러나 UML 다이어그램들은 가능한 한 쉽게 정보를 교환하기 위한 수단입니다; 그래서 사람들은 보통 수직 라인이나 수평 라인으로 동기화 상태를 그립니다; 단지 복잡한 상황일 때만( 예를 들어 화이트보드나 종이의 공간이 부족할 때 ) 동기화 상태가 이상한 각도로 그려질 수 있습니다.



스윔레인( Swimlanes )


활동 다이어그램에서 실제로 액션들을 실행하는 개체( 사람, 조직, 혹은 다른 책임 엔터티 ) 사이에서 활동의 절차적 제어 흐름을 모델링하는 것이 유용합니다. 이를 위해서, 당신은 스윔레인( swimlane )을 활동 다이어그램에 추가할 수 있습니다( 스윔레인은 수영 대회에서 둘 이상의 경쟁자들 사이에 있는 직선 경계와 닮았기 때문에 이름붙여졌습니다 ).


활동 다이어그램에 스윔레인을 넣기 위해서, 수직 열( column )을 사용합니다. 하나 이상의 액션을 실행하는 각 개체들은, 자신의 이름을 그 열의 최상위에 할당합니다. 그리고 나서 개체와 관련이 있는 액션들을 그 개체의 스윔레인에 배치합니다. Figure 12 는 액션들을 수행하는 두 개의 개체들을 보여 줍니다( Band Manager 와 Reporting Tool ). Band Manager 개체는 "Selects the View Sales For My Band Report" 액션을 실행하며, Reporting Tool 개체는 "Retrieve Bands the Band Manager Manages" 액션을 실행합니다. 비록 스윔레인이 활동 다이어그램의 명료성을 증진시켜주기는 하지만( 왜냐하면 모든 활동들이 자신의 실행 개체의 스윔레인에 배치되기 때문에 ), 이전에 언급한 활동 다이어그램을 관리하기 위한 모든 규칙들이 여전히 적용된다는 것을 기억하는 것이 중요합니다. 다시 말해, 당신은 스윔레인이 사용되지 않은 것처럼 활동 다이어그램을 읽으면 됩니다.


Figure 12: 두 개의 스윔레인은 Band Manager 개체의 액션을 Reporting Tool 개체의 액션과 구분합니다.



진보된 표기 요소들



지금까지 살펴 봤듯이, 액션들은 보통 개체에 의해 실행됩니다. 하지만 다른 액션에 대한 입력인 개체의 출력이 액션일 수 있습니다. UML 명세는 이 개체 입력/출력이 활동 다이어그램에서 모델링되도록 절대적인 요구를 하지는 않습니다만, 가끔은 그렇게 하는 것이 스윔레인을 다이어그램에서 제공하는 것이 유용한 것과 같은 이유로 유용합니다. 이러한 개체 흐름을 모델링하기 위해서, UML 은 액션-개체 흐름 관계( action-object flow relationship )라는 표기를 가지고 있는데, 이것은 두 가지 형태의 심볼을 포함합니다 -- 오브젝트 플로우( object flow )상태 개체( object in state ).



오브젝트 플로우( Object flow )


오브젝트 플로우는 전이 라인과 동일한 것입니다만, 채워진 선이 아니라 점선으로 표시됩니다. 오브젝트 플로우 라인은 상태 개체( object in state ) 심볼에 연결됩니다. 그리고 다른 오브젝트 플로우 라인은 상태 개체를 다음 액션과 연결합니다. Figure 13 은 액션-개체 흐름 관계를 보여 줍니다.


Figure 13: 액션-개체 흐름 관계


Figure 13 에서, 두 액션 사이의 전이 라인 대신에, 액션-개체 흐름 관계 심볼을 볼 수 있습니다. 이는 두 액션 사이에서 액션-개체 흐름 관계가 다른 액션으로의 전이를 내포하므로, 전이 라인들이 중복되는 것으로 고려되기 때문입니다( 역주 : 액션-개체 흐름 관계가 전이 라인을 대체하므로, 액션 사이에 전이 라인이 필요하지 않다는 의미. ).


상태 개체( Object in state )


상태 개체( Object in state ) 심볼은 Figure 13 에서 사각형으로 표현됩니다. 상태 개체 심볼의 첫 번째 부분은 밑줄친 텍스트입니다. 사각형에서 밑줄친 텍스트는 개체의 클래스 이름입니다 -- 우리 예제에서는 "Order" -- 그리고 이 클래스는 모델링된 시스템의 클래스 다이어그램에서 찾아볼 수 있을 것입니다. 상태 개체의 두 번째 부분은 괄호 내부의 텍스트인데, 이는 오브젝트의 상태 이름입니다. 오브젝트의 상태를 포함하는 것은 선택적입니다만, 액션이 개체의 상태를 변경한다면 오브젝트 상태를 포함시키는 것을 추천합니다. Figure 14 는 다중의 액션-개체 흐름 관계들을 가진 완전한 활동 다이어그램을 보여 줍니다.


Figure 14: "Place Order" 액션은 Order 개체를 "Placed" 상태로 만듭니다;

그리고 나서 "Verify Ordered Products Are In Stock" 액션은 Order 개체를 "Accepted" 상태로 옮깁니다.


액션-개체 흐름 관계들을 포함하는 것은 활동 다이어그램을 읽는 방식을 변경시키지 않습니다; 이것은 그냥 부가적인 정보를 제공합니다. Figure 14 에서 "Place Order" 액션은 Order 개체에 "Placed" 상태를 설정합니다; 나중에 "Verify Ordered Products Are In Stock" 액션은 Order 오브젝트를 "Accepted" 상태로 옮깁니다. 우리는 이러한 액션들이 Order 의 상태를 수정하는 것을 알고 있습니다. 왜냐하면 상태 개체 심볼들이 상태들을 가지기 때문입니다.



하위 활동 상태( Subactivity state )


하위 활동 상태( subactivity state ) 는 활동의 흐름에 다른 활동을 내포하고자 할 때 사용할 수 있는 또 다른 상태 다이어그램입니다. 하위 활동 상태는 활동 다이어그램에서 액션 상태가 위치한 곳에 배치됩니다. 액션 상태와 같은 방식으로 하위 활동 상태를 그릴 수 있는데, 우하단에 내포된 활동 다이어그램임을 표현하는 아이콘이 추가되어 있습니다. 하위 활동 상태의 이름은 Figure 15 와 Figure 16 에서 볼 수 있듯이 심볼 내에 위치합니다.


Figure 15 : IBM Rational XDETM 으로 그려진 하위 활동 상태의 예.


Figure 16: UML 명세에 그려진 하위 활동 상태의 예.


Figure 15 와 Figure 16 에서 우하단에 있는 하위 활동 상태 아이콘들이 약간 다르다는 점에 주목하십시오. UML 1.4 명시적으로 어떤 아이콘이 사용되어야 하는지에 대한 언급이 없습니다. 대신에 다음과 같이 이야기하고 있습니다:


하위 활동 상태는 액션 상태와 동일한 방식으로 표현되는데, 우하단에 내포된 활동 다이어그램을 표현하는 아이콘을 가지고 있다. 하위 활동 상태의 이름은 심볼 내에 위치한다. 하위 활동 상태는 다이어그램 내에서 유일할 필요는 없다. 이 표기법은 "내포된" 구조를 지원하는 모든 UML 생성에 적용된다. 아이콘은 내포된 구조의 형식을 암시해야만 한다.


지금으로썬 이것이 표준 심볼이 없다는 것을 의미합니다. 왜냐하면 하위 활동 상태 아이콘들이 누가( 혹은 어떤 도구가 ) 그것들을 활동 다이어그램에 넣느냐에 의존하기 때문입니다. 그래서 이 아이콘의 외형에 대한 확고한 동의에 도달할 때까지는 단순하게 일관성있는 것이 최선입니다: 하위 활동 상태를 표현할 때마다 같은 아이콘을 사용하는 것이 좋습니다.



결론



다른 UML 다이어그램들과 마찬가지로, 활동 다이어그램의 첫 번째 목적은 정보를 효율적으로 교환하는데 있습니다. 활동 다이어그램을 전체 시스템 모델에 포함하는 주요 이유는 그것들이 다양한 활동을 위한 절차적인 제어 흐름을 모델링한다는 것입니다. 이는 이러한 종류의 모델이 비즈니스를 하는 사람들이 시스템이 동작하는 비즈니스 환경에 대한 이해를 더 잘할 수 있게 해 주기 때문에 중요합니다. 물론, 활동 다이어그램은 비즈니스 처리를 모델링하는데만 국한되는 것은 아닙니다; 그것들은 컴퓨터 프로세스를 모델링하는 데도 사용될 수 있습니다.


일반적으로, 자신만의 활동 다이어그램을 생성할 때 이 기사에서 설명한 모든 표기 요소들을 사용하지는 않을 것입니다. 하지만 초기 상태, 전이 라인, 액션 상태, 최종 상태 표기들은 매우 자주 사용할 것입니다.


필수 UML 다이어그램에 대한 이 시리즈의 다음 기사에서는, 클래스 다이어그램에 대해서 세부적으로 다룰 것입니다. 올 가을에 다시 만납시다.


프리즘 4.1 도움말 번역입니다




진보된 MVVM 시나리오들



이전 챕터에서는, 응용프로그램 유저 인터페이스( UI ), 프리젠테이션 로직, 비즈니스 로직을 세 개의 분리된 클래스( 뷰, 뷰 모델, 모델 )로 분리하고, 그 클래스들 간의 상호작용을( 데이터 바인딩, 커맨드, 데이터 유효화 인터페이스를 통해서 ) 구현하고, 생성과 엮기( wire-up )를 다루기 위한 전략을 구현함으로써 모델-뷰-뷰모델 패턴을 구현하는 방법을 설명했습니다. 


이러한 기본 요소들을 구현하는 MVVM 패턴은 응용프로그램 내의 많은 시나리오들을 지원할 수 있는 가능성이 있을 것입니다. 그러나, 기본 MVVM 패턴을 확장할 것을 요구하거나 더욱 진보된 기법들이 적용될 것을 요구하는 복잡한 시나리오들에 직면할 수도 있습니다. 이것은 응용프로그램이 더 커지거나 복잡해지면 사실이 될 가능성이 있습니다. 그러나 이러한 시나리오는 많은 작은 응용프로그램들에서도 만날 수 있는 문제입니다. 프리즘 라이브러리는 이런 많은 기법들을 구현하는 컴포넌트들을 제공하며, 자신만의 응용프로그램에서 그것들을 더욱 쉽게 사용할 수 있도록 해 줍니다.


이 챕터는 약간 복잡한 시나리오들을 설명하며, 그것들을 MVVM 패턴이 그것들을 지원하는 방법에 대해서 설명합니다. 다음 섹션은 커맨드가 서로 연결되거나 차일드 뷰들과 연관되는 방식과 그것들이 커스텀 요구사항들을 지원하기 위해서 확장될 수 있는 방법에 대해서 설명합니다. 그리고 나서 그 다음 섹션들은 비동기 데이터 요청과 연속되는 UI 상호작용을 다루는 방법과 뷰와 뷰 모델 사이의 상호작용 요청을 다루는 방법에 대해서 설명합니다.


"진보된 생성 및 엮기" 섹션은 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용할 때 생성과 엮기를 다루는 가이드를 제공합니다. 마지막 섹션은 응용프로그램의 뷰 모델과 모델을 단위 테스트하고 비헤이비어를 테스트하는 가이드를 제공함으로써 MVVM 응용프로그램을 테스트하는 방법을 설명합니다.



커맨드들



커맨드는 커맨드의 구현 로직을 UI 표현과 분리하는 방법을 제공합니다. 데이터 바인딩이나 비헤이비어는 뷰의 엘리먼트들을 뷰 모델에 의해 제공되는 커맨드들과 선언적으로 연관시켜주는 방법을 제공합니다. 챕터 5 "MVVM 패턴 구현하기" 의 "커맨드들" 섹션은 커맨드가 뷰 모델에서 커맨드 오브젝트나 커맨드 메서드로 구현될 수 있는 방법과 그것들이 뷰의 컨트롤에서 혹은 비헤이비어를 사용해서 혹은 특정 컨트롤에 의해 제공되는 내장 Command 프라퍼티를 사용해서 호출될 수 있는 방법에 대해서 설명합니다.


노트:

WPF 라우티드( routed ) 커맨드들: MVVM 패턴에서 커맨드 오브젝트나 커멘드 메서드로서 구현되는 커맨드들은 라우티드 커맨드라는 이름을 가진 WPF 의 내장 커맨드 구현과는 다소 다르다는 것을 기억해야 합니다( 실버라이트는 라우티드 커맨드 구현을 가지지 않습니다 ). WPF 라우티드 커맨드는 UI 트리(  특히 논리 트리 )의 엘리먼트들을 통해 커맨드들을 라우팅함으로써 커맨드 메시지를 받습니다. 그러므로, 커맨드 메시지들은 해당 요소로부터 혹은 명시적으로 지정된 타깃 엘리먼트들까지 위 아래로 라우팅됩니다; 기본적으로, 그것들은 뷰와 연관된 뷰 모델과 같은 UI 트리 외부의 컴포넌트까지 라우팅되지는 않습니다. 그러나 WPF 라우티드 커맨드들은 뷰의 코드-비하인드에 정의된 커맨드 핸들러를 사용해서뷰 모델 클래스로 커맨드 호출을 전달할 수 있습니다.



복합 명령들


많은 경우에, 뷰 모델에 의해 정의된 커맨드는 연관된 뷰의 컨트롤에 바인딩 될 것이며, 그래서 사용자가 직접적으로 뷰 내부에서 커맨드를 실행할 수 있습니다. 그러나 어떤 경우에는, 응용프로그램 UI 의 부모 뷰에 있는 컨트롤로부터 하나 이상의 뷰 모델의 커맨드들을 실행할 수 있기를 원할 수도 있습니다.


예를 들어, 만약 응용프로그램이 사용자로 하여금 동시에 다수의 아이템들을 편집할 수 있도록 허용했다고 한다면, 당신은 응용프로그램의 툴바나 리본에 있는 버튼에 의해 표현되는 단일 커맨드를 사용해 사용자가 모든 아이템들을 저장할 수 있도록 허용하기를 원할 것입니다. 이 경우, Save All 커맨드는 각 아이템을 위한 뷰 모델 인스턴스에 의해 구현된 Save 커맨드들을 각각 실행하게 될 것입니다. 그 예가 아래 그림에 나와 있습니다.


SaveAll 복합 커맨드를 구현하기




프리즘은 CompositeCommand 클래스를 통해 이 시나리오를 지원합니다.


CompositeCommand 클래스는 다중 자식 커맨드들로 구성되는 커맨드를 표현합니다. 복합 커맨드가 실행되면, 그것의 자식 커맨드들이 순서대로 실행됩니다. 이는 UI 에서 단일 커맨드로서 커맨드 그룹을 표현할 필요가 있거나 다중 커맨드들을 하나의 논리 커맨드로 구현하기를 원할 때 유용합니다.


예를 들어, CompositeCommand 클래스는 buy/sell view 의 Submit Alll 버튼으로 표현되는 SubmitAllOrders 를 구현하기 위해서 Stock Trader Reference Implementation( Stock Trader RI ) 에서 사용되는 클래스입니다. 사용자가 Submit All 버튼을 클릭하면, 개별 buy/sell 트랜잭션에 의해 정의된 개별 SubmitCommand 가 실행됩니다.


CompositeCommand 클래스는 자식 커맨드들의 리스트를 유지합니다( 그것들은 DelegateCommand 인스턴스들입니다 ). CompositeCommand 의 Execute 메서드는 단지 자식 커맨드들의 Execute 메서드를 순서대로 호출할 뿐입니다. CanExecute 메서드는 이와 유사하게 개별 자식 커맨드들의 CanExecute 메서드에 대한 호출을 하지만, 하나의 자식 커맨드라도 실행될 수 없는 상태라면 CanExecute false 를 반환하게 될 것입니다. 다시 말해, 기본적으로, CompositeCommand 는 모든 자식 커맨드들이 실행될 수 있을 때만 실행됩니다.



자식 커맨드들을 등록하고 등록해제하기


자식 커맨드들은 RegisterCommand UnregisterCommand 메서드를 통해서 등록되고 등록해제됩니다. 예를 들면 Stock Trader RI 에서, 각 buy/sell 주문에 대한 Submit 과 Cancel 커맨드는 SubmitAllOrders 와 CancelAllOrders 복합 커맨드에 등록됩니다. 아래 코드는 그 예를 보여 줍니다( OrderController 클래스를 참조하십시오 ).



노트:

앞의 commandProxy 오브젝트는 Submit 와 Cancel 복합 커맨드에 접근하는 인스턴스를 제공하는데, 그것은 정적으로 정의되어 있습니다. 더 많은 정보를 원한다면, StockTraderRICommands.cs 파일의 클래스를 참조하십시오.



활성화된 자식 뷰들에서 커맨드 실행하기


보통, 응용프로그램은 응용프로그램 UI 내의 자식 뷰 컬렉션을 출력할 필요가 있습니다. 여기에서 각 자식 뷰는 하나 이상의 커맨드를 구현하는 연관된 뷰 모델을 가지고 있을 것입니다. 복합 커맨드들은 응용프로그램 UI 내의 자식 뷰들에 의해 구현된 커맨드들을 표현하기 위해 사용될 수 있으며, 이는 그것들이 부모 뷰에서 실행되는 방법을 조직화하는데 도움을 줍니다. 이 시나리오들을 지원하기 위해서, 프리즘 CompositeCommand DelegateCommand 클래스는 프리즘 리전( region )과 함께 동작하도록 설계되어 왔습니다.


프리즘 리전( 챕터 7 "유저 인터페이스 만들기" 의 "리전들" 에 설명되어 있습니다 )은 자식 뷰들이 응용프로그램 UI 의 논리적 플레이스홀더( placeholder ) 와 연관되는 방법을 제공합니다. 그것들은 보통 자식 뷰들의 특정 레이아웃을 UI 에서의 위치 및 논리적 플레이스홀더와 디커플링하기 위해서 사용됩니다. 다음 그림은 각 자식 뷰들이 EditRegion 이라 명명된 리전에 추가되고 UI 디자이너가 그 리전에 뷰를 배치하기 위해서 Tab 컨트롤을 사용하는 것을 보여 줍니다.


Tab 컨트롤을 사용해 EditRegion 정의하기




부모 뷰 레벨에서 복합 커맨드는 보통 자식 뷰 수준에서 커맨드가 실행되는 방법을 조직화하기 위해 사용될 것입니다. 어떤 경우에는, 이전에 설명된 Save All 커맨드 예제에서 처럼, 당신은 보여지는 모든 뷰를 위해 커맨드가 실행되기를 원할 것입니다. 다른 경우에는, 당신은 커맨드들이 활성화된 뷰 상에서만 실행되기를 원할 것입니다. 이 경우, 복합 커맨드는 활성화되었다고 여겨지는 뷰 상에서만 자식 커맨드들을 실행할 것입니다. 예를 들어, 당신은 응용프로그램의 툴바나 리본의 Zoom 커맨드를 구현하는데, 그것이 현재 활성화된 아이템에서만 적용되도록 하기를 원할 것입니다. 다음 다이어그램은 그것을 보여 줍니다.


Tab 컨트롤을 사용하여 EditRegion 을 정의하기




이 시나리오를 지원하기 위해서, 프리즘은 IActiveAware 인터페이스를 제공합니다. IActiveAware 인터페이스는 구현자가 활성화되어 있을 때 true 를 반환하는 IsActive 프라퍼티를 정의하며, 그 활성화 상태가 변경될 때마다 발생하는 IsActiveChanged 이벤트를 정의합니다.


자식 뷰나 뷰 모델에서 IActiveAware 인터페이스를 구현할 수 있습니다. 그것은 주로 리전 안의 자식 뷰의 활성화 상태를 트래킹하는데 사용됩니다. 뷰가 활성화되어 있는지 여부는 특정 리전 컨트롤 내의 뷰를 조직하는 리전 어댑터에 의해 결정됩니다. 예를 들어 이전에 보여 준 Tab 컨트롤에 대해, 현재 선택된 탭에서 서 뷰가 활성화되어 있다고 설정하는 리전 어댑터가 존재합니다.


DelegateCommand 클래스도 IActiveAware 인터페이스를 구현합니다. CompositeCommand 는 ( CanExecute 와는 별개로 ) 생성자에서 monitorCommandActivity 파라미터를 true 로 설정함으로써 자식 DelegateCommands 의 활성화 상태를 평가하기 위해서 구성될 수 있습니다. 이 파라미터가 true 로 설정되면, CompositeCommand 클래스는 CanExecute 메서드를 위해 반환값을 결정할 때와 Execute 메서드 내에서 자식 커맨드들이 실행될 때 각 자식 DelegateCommand 의 활성화 상태를 고려할 것입니다.


monitorCommandActivity 파라미터가 true 이면, CompositeCommand 클래스는 다음과 같은 동작들을 보여 줍니다:

  • CanExecute. 모든 활성화된 커맨드들이 실행될 수 있을 때만 true 를 반환합니다. 비활성화된 자식 커맨드들은 전혀 고려되지 않습니다.
  • Execute. 모든 활성화된 커맨드들을 실행합니다. 비활성화된 자식 커맨드들은 전혀 고려되지 않습니다.

이 기능을 사용해서 앞서 설명한 예제를 구현할 수 있습니다. IActiveAware 인터페이스를 자식 뷰나 모델에 구현함으로써, 당신은 자식 뷰가 영역에서 활성화되거나 비활성화되는 때를 통지받을 수 있습니다. 자식 뷰의 활성화 상태가 변경되면, 자식 커맨드의 활성화 상태를 갱신할 수 있습니다. 그리고 나서 사용자가 Zoom 복합 커맨드를 실행하면, 활성화된 자식 뷰의 Zoom 커맨드가 실행될 것입니다.


컬렉션들 내부의 커맨드들

뷰에서 아이템 컬렉션을 출력할 때 당신이 자주 직면하게 되는 다른 일반적인 시나리오는 컬렉션의 각 아이템을 위한 UI 를 ( 아이템 레벨이 아니라 ) 부모 뷰 레벨에서 커맨드와 연관시킬 필요가 있을 때입니다.


예를 들어, 다음 그림에서 보여 주는 응용프로그램에서, 뷰는 ListBox 컨트롤에서 아이템 컬렉션을 출력합니다. 그리고 사용자가 컬렉션에서 개별 아이템들을 제거할 수 있도록 하는 Delete 버튼을 정의하는 아이템을 출력하기 위해서 데이터 템플릿을 사용합니다.


컬렉션 내의 커맨드들을 바인딩하기




뷰 모델이 Delete 커맨드를 구현하기 때문에, 각 아이템을 위한 UI 의 Delete 버튼을 뷰 모델에 의해 구현된 Delete 커맨드와 엮는 것이 도전 과제입니다. ListBox 의 각 아이템들에 대한 데이터 칸텍스트가 Delete 커맨드를 구현하는 부모 뷰 모델이 아니라 컬렉션 내의 아이템들을 참조하고 있는 것이 어려운 점입니다.


이 문제를 해결하는 한 가지 접근법은, 데이터 템플릿이 아니라 부모 컨트롤에 상대적인 바인딩을 보장하도록 하기 위해, ElementName 바인딩 프라퍼티를 사용하여 부모 뷰의 커맨드에 데이터 템플릿의 버튼을 바인딩하는 것입니다. 다음 XAML 은 이 기법을 설명하고 있습니다.



데이터 템플릿의 버튼 컨트롤의 내용은 컬렉션 아이템의 Name 프라퍼티에 바인딩된다. 하지만 버튼을 위한 커맨드는 root 엘리먼트의 데이터 칸텍스트를 통해 Delete 커맨드에 바인딩됩니다. 이는 버튼이 아이템 레벨이 아니라 부모 뷰 레벨에서 커맨드에 바인딩될 수 있도록 허용해 줍니다. 당신은 CommandParameter 프라퍼티를 사용해서 아이템에 적용될 커맨드가 무엇인지를 지정하거나, ( CollectionView 를 통해 ) 현재 선택된 아이템에서 수행될 커맨드를 구현할 수 있습니다.



커맨드 비헤이비어


실버라이트 3 이전 버전에서, 실버라이트는 직접적으로 커맨드들을 지원하는 컨트롤들을 제공하지 않았습니다. ICommand 인터페이스가 이용 가능했지만, 컨트롤들이 ICommand 구현에 직접적으로 연결될 수 있도록  허용해주는 Command 프라퍼티를 구현한 컨트롤이 존재하지 않았습니다. 이 한계를 극복하기 위해서, 그리고 실버라이트 3 에서 MVVM 커맨딩 패턴들을 지원하기 위해서, 프리즘 라이브러리( 버전 2.0 )은 모든 실버라이트 컨트롤들이 연결된 비헤이비어를 사용해 커맨드 오브젝트에 바인딩될 수 있도록 허용하는 메커니즘을 제공했습니다. 이 메커니즘은 WPF 에서도 작동하는데, 이는 뷰 모델 구현들이 실버라이트와 WPF 에서 재사용될 수 있도록 해 주었습니다.


다음 예제는 버튼의 클릭 이벤트에 뷰에서 정의한 커맨드 오브젝트가 바인딩될 수 있도록 하기 위해서 프리즘 커맨드 비헤이비어가 사용되는 방식을 보여 줍니다.



실버라이트 4 는 모든 Hyperlink-상속 컨트롤들과 ButtonBase-상속 컨트롤들을 위해 Command 프라퍼티를 위한 지원을 추가했으며, 이는 그것들이 WPF 에서와 같은 방식으로 커맨드 오브젝트들에 바인딩될 수 있도록 해 줬습니다. 이러한 컨트롤들을 위해서 Command 프라퍼티를 사용하는 것은 챕터 5 "MVVM 패턴 구현하기" 의 "커맨드들" 섹션에서 설명되고 있습니다. 그러나 프리즘 커맨드 비헤이비어는 하위 호환성과 커스텀 비헤이비어의 개발을 지원하기 위해서 남아 있으며, 이는 뒤에서 설명됩니다.


이 비헤이비어 접근법은 뷰의 컨트롤에 쉽게 적용될 수 있는 인터랙티브 비헤이비어를 구현하고 캡슐화하기 위해서 범용적으로 이용 가능한 기법입니다. 이전에 보여 준 커맨드를 지원하기 위해서 비헤이비어를 사용하는 것은 비헤이비어가 지원할 수 있는 많은 시나리오들 중의 하나일 뿐입니다. Microsoft Expression Blend 는 이제 챕터 5 "MVVM 패턴 구현하기" 의 "뷰에서 커맨드 메서드 실행하기" 섹션에서 설명했던 InvokeCommandAction CallMethodAction 을 포함하는 다양한 비헤이비어들을 제공합니다. 그리고 커스텀 비헤이비어를 개발하기 위한 소프트웨어 개발 키트( SDK )를 제공합니다. Expression Blend 는 비헤이비어를 위해 드래그-앤-드랍 생성 및 프라퍼티 편집 지원을 제공하는데, 이는 비헤이비어 추가 작업을 쉽게 만들어 줍니다. 커스텀 Expression Blend 비헤이비어를 개발하는 것에 대한 더 많은 정보를 원한다면, MSDN 의 "Creating Custom Behavior" 를 참조하십시오.


실버라이트 4 에서 커맨드-이용 가능한 컨트롤들에 대한 지원을 소개하고, Expression Blend Behaviors SDK 를 소개하기는 했지만, 프리즘 커맨드 비헤이비어에 대한 대단한 요구들이 있는 것은 아닙니다. 당신은 아마도 그것들의 치밀한 구문과 구현들, 그리고 쉽게 확장될 수 있는 능력과 유용성을 발견하게 될 것입니다.



프리즘 커맨드 비헤이비어들을 확장하기


프리즘 커맨드 비헤이비어는 어태치드( attached ) 비헤이비어 패턴에 기반하고 있습니다. 이 패턴은 컨트롤에 의해 발생되는 이벤트들을 뷰 모델에 의해 제공되는 커맨드 오브젝트에 연결합니다. 프리즘 커멘드 비헤이비어는 두 개의 부분으로 구성됩니다; 어태치드 프라퍼티와 비헤이비어 오브젝트. 어태치드 프라퍼티는 타깃 컨트롤과 비헤이비어 오브젝트 간의 관계를 수립합니다. 비헤이비어 오브젝트는 타깃 컨트롤을 관찰하며, 컨트롤이나 뷰 모델 내의 이벤트들이나 상태 변경들에 기반한 동작을 수행합니다.


프리즘 커맨드는 ButtonBase-상속 컨트롤들의 Click 이벤트에 기반합니다. 이는 ButtonBaseClickCommandBehavior 클래스와 타깃 컨트롤의 클릭 이벤트에 그것을 연결하는 어태치드 프라퍼티를 제공함으로써 가능해 집니다. 아래 그림은 ButtonBase, ButtonBaseClickCommandBehavior, 뷰 모델에 의해 제공되는 ICommand 오브젝트의 관계를 보여 줍니다.


ButtonClick 이벤트를 ICommand 에 전달하기





당신의 응용프로그램은 ButtonBase 로부터의 Click 이벤트가 아니라 컨트롤이나 이벤트로부터의 커맨드들을 실행할 필요가 있습니다. 혹은 비헤이비어가 타깃 컨트롤과 상호작용하는 방식이나 뷰 모델이 그것에 바인딩되는 방식을 커스터마이즈할 필요가 있습니다. 이런 경우에, 당신은 자신만의 어태치드 프라퍼티나 비헤이비어 구현을 정의할 필요가 있습니다.


프리즘 라이브러리는 CommandBehaviorBase<T> 클래스를 제공해서 ICommand 오브젝트들과 상호작용하는 비헤이비어들을 생성하기 쉽게 만들어 줍니다. 이 클래스는 커맨드를 실행하고 커맨드의 CanExecuteChanged 이벤트의 변경을 관찰하고, 실버라이트와 WPF 에서 커맨드 지원을 확장하는데 사용될 수 있습니다.


커스텀 비헤이비어를 생성하려면, CommandBehaviorBase<T> 를 상속하는 클래스를 생성하고 당신이 관찰하고 싶은 컨트롤을 타깃으로 설정하십시오. 이 클래스의 타입 파라미터는 비헤이비어가 연결된 컨트롤의 타입을 식별합니다. 클래스의 생성자에서, 당신은 컨트롤로부터 관찰하고자 하는 이벤트들을 구독할 수 있습니다. 다음 코드 예제는 ButtonBaseClickCommandBehavior 클래스의 구현을 보여 줍니다.



CommandBehaviorBase<T> 클래스를 사용하면, 자신만의 커스텀 비헤이비어 클래스를 정의할 수 있습니다; 이는 타깃 컨트롤이나 뷰 모델에 의해 제공되는 커맨드들과 비헤이비어가 상호작용하는 방식을 커스터마이즈 할 수 있도록 해 줍니다. 예를 들어, 당신은 다양한 컨트롤 이벤트에 기반해 바인딩된 커맨드를 실행하거나 바인딩된 커맨드의 CanExecute 상태에 기반해 컨트롤의 가시적 상태를 변경하는 비헤이비어를 정의할 수 있습니다.


타깃 컨트롤에 커맨드 비헤이비어를 선언적으로 어태치하는 것을 지원하기 위해서, 어태치드 프라퍼티가 사용됩니다. 어태치드 프라퍼티는 비헤이비어가 XAML 에서 컨트롤에 어태치되는 것을 허용하며, 타깃 컨트롤과 비헤이비어 구현의 연관 및 생성을 관리합니다. 어태치드 프라퍼티는 정적 클래스에서 정의됩니다. 프리즘 커맨드 비헤이비어는 어떤 규약에 기반하는데, 그것은 커맨드를 실행하기 위해서 사용되는 이벤트를 가리키는 정적 클래스의 이름입니다. 어태치드 프라퍼티의 이름은 데이터 바인딩되고 있는 오브젝트의 타입을 가리킵니다. 그러므로 앞서 설명한 프리즘 커맨드 비헤이비어는 Click 이라 명명된 정적 클래스를 사용합니다. 이 클래스는 Command 라고 명명된 어태치드 프라퍼티를 정의합니다. 이는 앞에서 보여 준 Click.Command 라는 구문을 사용하는 것을 허용합니다.


커맨드 비헤이비어 자체가 어태치드 프라퍼티를 통해 타깃 컨트롤과 실제로 연관되기도 합니다. 그러나 이 어태치드 프라퍼티는 정적 클래스에 대해 private 이며, 개발자는 볼 수 없습니다.



Command 어태치드 프라퍼티의 구현은 ButtonBaseCommandBehavior 클래스의 인스턴스를 생성합니다. 이는 OnSetCommandCallback 콜백 메서드를 통해서 이루어 집니다. 그것이 아래 코드 예제에 나와 있습니다.



어태치드 프라퍼티에 대한 더 많은 정보를 원한다면 MSDN 의 "Attached Properites Overview" 를 참조하십시오.



비동기 상호작용들을 다루기



보통 뷰 모델들을 비동기적으로 통신하는 서비스들이나 컴포넌트들과 상호작용할 필요가 있습니다.이는 당신이 실버라이트 응용프로그램을 다루거나, 웹서비스타 네트워크를 통한 다른 리소스들과 상호작용하거나, 응용프로그램이 계산이나 I/O 를 수행하기 위해서 백그라운드 작업을 사용할 때 특히 그렇습니다. 이러한 연산을 비동기적으로 수행하는 것은 응용프로그램이 좋은 사용자 경험을 배달하는데 필수적인 응답성을 유지하고 있음을 보장해 줍니다.


사용자가 비동기적인 요청이나 백그라운드 작업을 초기화할 때, 그 응답이 언제 도착할지와 스레드가 언제 반환될지를 예측하는 것은 어렵습니다. UI 는 UI 스레드에서만 갱신될 수 있기 때문에, 당신은 UI 스레드 상에서 요청을 디스패치함으로써 UI 를 갱신해야 할 것입니다.



웹 서비스들에서 데이터를 획득하고 상호작용하기


웹 서비스나 원격 접근 기법들과 상호작용할 때, 당신은 보통 IAsyncResult 패턴을 사용할 것입니다. 이 패턴에서는 GetQuestionnaire 같은 메서드를 호출하는 대신, BeginGetQuestionnaire EndGetQuestionnaire 와 같은 메서드 쌍을 사용합니다. 비동기 호출을 초기화하기 위해서 BeginGetQuestionnaire 를 호출합니다. 결과를 획득하거나 타깃 메서드를 실행하는 도중에 예외가 발생하는지를 확인하기 위해서, 호출이 완료되었을 때 EndGetQuestionnaire 를 호출합니다.


EndGetQuestionnaire 의 호출 시점을 결정하려면, BeginGetQuestionnaire 를 호출하는 동안에 사용할 콜백을 지정하거나 완료되었는지 상황을 조사할 수 있습니다. 콜백 접근법을 사용하면, 당신의 콜백 메서드가 타깃 메서드 실행이 완료되었을 때 호출될 것입니다. 그러면 거기에서 EndGetQuestionnaire 를 호출합니다. 그 예가 아래에 나와 있습니다.



End 메서드( 이 경우 EndGetQuestionnaire )에 대한 호출에서 주의할 점이 있습니다. 요청을 실행하는 동안에 발생한 예외가 실행될 수도 있다는 것입니다. 당신의 응용프로그램은 반드시 이를 처리해야만 하며, UI 를 통해 스레드 안전한 방법으로 그들에게 보고해 줘야할 필요가 있을 것입니다. 만약 이것들을 다루지 않는다면, 스레드는 종료될 것이고, 당신은 결과를 처리할 수 없게 될 것입니다.


응답이 항상 UI 스레드에서 발생하는 것은 아니기 때문에, 당신이 UI 상태에 영향을 줄 수 있는 무엇인가를 변경하려고 하는 계획득 세웠다면, 스레드 Dispatcher 오브젝트나 SynchronizationContext 오브젝트를 사용해서 UI 스레드에 그 응답을 디스패치해야 할 것입니다. WPF 와 실버라이트에서는 보통 디스패처를 사용할 것입니다.


다음 코드 에제에서, Questionnaire 오브젝트는 비동기적으로 획득되며, 그것은 QuestionnaireView 를 위한 데이터 컨텍스트로서 설정됩니다. 실버라이트에서 당신은 디스패처의  CheckAccess 메서드를 사용해서 당신이 지금 UI 스레드 상에서 작업하고 있는지 여부를 확인할 수 있습니다. 만약 UI 스레드가 아니라면, BeginInvoke 메서드를 사용해서 UI 스레드로 그 요청이 운반되도록 할 필요가 있습니다.



Model-View-ViewModel Reference Implementation( MVVM RI ) 는 IAsyncResult-상속 서비스 인터페이스를 이전 예제와 유사한 방식으로 사용하는 방식의 예를 보여 줍니다. 이는 고객을 위해 더 단순한 콜백 메커니즘을 제공하기 위해서 서비스를 래핑하고, 호출자의 스레드에 콜백을 디스패치하기 하는 것을 다룹니다. 예를 들어, 다음 코드 예제는 설문지( questionnaire )를 획득하는 것을 보여 줍니다.



반환되는 result 오브젝트는 발생할 수 있는 에러들과 함께 획득한 결과를 래핑합니다. 다음 코드 예제는 에러가 평가되는 방법을 보여 줍니다.




유저 인터페이스 패턴들



응용프로그램들에서는 이벤트의 발생을 사용자에게 통지하거나 작업을 진행하기 전에 확인을 요청할 필요성이 빈번하게 발생합니다. 이러한 상호작용들은 보통 응용프로그램에서의 변화를 사용자들에게 단순하게 전달하기 위해서 설계된 짧은 상호작용이거나 사용자들로부터 간단한 응답을 획득합니다. 이러한 상호작용들 중의 일부는 사용에게 다이얼로그 박스나 메시지 박스를 출력할 때와 같이 modal 로 나타납니다. 혹은 토스트 통지나 팝업 윈도우처럼 

non-modal 로 나타납니다.


이러한 경우에 사용자와 상호작용하는 여러 가지 방법들이 존재하지만, 그것들을 명확한 관심사 분리를 보존하는 방식으로 MVVM 기반 응용프로그램에서 구현하는 것은 어려운 일이 될 수 있습니다. 예를 들어, non-MVVM 응용프로그램에서, 응답을 위한 프롬프트를 띄우기 위해 코드-비하인드 파일에서 MessageBox 클래스를 사용할 것입니다. MVVM 응용프로그램에서, 이는 적절하지 않습니다. 왜냐하면 그것은 뷰와 뷰 모델 간의 관심사 분리를 깰 것이기 때문입니다.


MVVM 패턴의 관점에서, 뷰 모델은 사용자의 상호작용을 초기화할 책임과 모든 응답을 처리할 책임이 있고, 반면에 뷰는 사용자 경험에 적절한 무엇인가를 사용해 사용자와의 상호작용을 실제로 관리할 책임이 있습니다. 뷰 모델에 구현된 프리젠테이션 로직과 뷰에 의해 구현된 사용자 경허 사이의 관심사 분리를 보존하는 것은 테스트 용이성과 유연성을 증대시킵니다.


MVVM 에는 이러한 종류의 사용자 상호작용을 구현하기 위한 두 가지 일반적인 접근법이 존재합니다. 한 접근법은 사용자와의 상호작용을 초기화하기 위해서 뷰 모델에 의해 사용될 수 있는 서비스를 구현하는 것이며, 그래서 뷰의 구현에 대한 의존성을 서비스의 의존성을 보존하는 것입니다. 다른 접근법은 사용자와의 상호작용에 대한 의도를 표현하기 위해서 뷰 모델에 의해 발생되는 이벤트들을 사용하는 것과 더불어, 이러한 이벤트들에 바인딩되고 상호작용에 대한 가시적 관점을 관리하는 뷰의 컴포넌트를 사용하는 것입니다. 각 접근법은 다음 섹션들에 설명됩니다.



상호작용 서비스 사용하기


이 접근법에서, 뷰 모델은 메시지 박스를 통해 사용자와의 상호작용을 초기화하기 위해서 상호작용 서비스 컴포넌트에 의존합니다. 이 접근법은 관심사의 명확한 분리와 테스트 용이성을 지원하는데, 이는 상호작용의 가시적 구현을 개별 서비스 컴포넌트에 캡슐화함으로써 수행됩니다. 일반적으로, 뷰 모델은 상호작용 서비스 인터페이스에 대한 의존성을 가집니다. 그것은 의존성 주입이나 서비스 로케이터를 통해 상호작용 서비스의 구현에 대한 참조를 빈번하게 요청합니다.


뷰 모델이 상호작용 서비스에 대한 참조를 가지게 되면, 그것은 프로그램적으로 필요할 때마다 사용자와의 상호작용을 여청할 수 있습니다. 상호작용 서비스는 상호작용의 가시적 관점을 구현하는데, 이는 다음 그림에 나와 있습니다. 뷰 모델에서 인터페이스 참조를 사용하는 것은 사용자 인터페이스의 구현 요구사항에 따라 여러 가지 구현이 사용될 수 있도록 허용해 줍니다. 예를 들어, WPF 와 실버라이트를 위한 상호작용 서비스의 구현이 제공될 수도 있는데, 이는 응용프로그램의 로직의 재사용성을 많이 높여 줍니다.


사용자와의 상호작용을 위해서 상호작용 서비스를 사용하기



Modal interactions, such as where the user is presented with MessageBox or modal pop-up window to obtain a specific response before execution can proceed, can be implemented in a synchronous way, using a blocking method call, as shown in the following code example.



그러나 이접근법의 단점은 동기적인 프로그래밍 모델을 강제한다는 것인데, 이는 상호작용 서비스를 구현할 때 실버라이트에서 다른 상호작용 메커니즘들과는 공유되지 않고, 매우 많은 어려움을 겪게 만듭니다. 대안적인 비동기적 구현은 상호작용이 완료되었을 때 실행될 콜백을 뷰 모델이 제공하도록 허용하는 것입니다. 다음 코드는 이러한 접근법을 설명합니다.



비동기 접근법은 modal 및 non-modal 상호작용이 구현될 수 있도록 허용함으로써 상호작용 서비스를 구현할 때 유연성을 증대시켜 줍니다. 예를 들어, WPF 에서, MessageBox 클래스는 진짜로 사용자와의 modal 상호작용 구현하기 위해서 사용될 수 있습니다; 반면에, 실버라이트에서, 팝업 윈도우는 사용자와의 pseudo-modal 상호작용을 구현하기 위해서 사용될 수 있습니다.



상호작용 요청 오브젝트들을 사용하기


MVVM 에서 단순한 사용자 상호작용을 구현하기 위한 다른 접근법은, 뷰의 비헤이비어와 쌍을 이루는 상호작용 오브젝트를 통해서, 뷰 모델이 뷰 자체에 대한 상호작용 요청을 직접적으로 만드는 것을 허용하는 것입니다. 이 상호작용 요청 오브젝트는 이벤트들을 통해 상호작용 요청, 그것의 응답을 캡슐화하며, 뷰와 통신합니다. 뷰는 상호작용에 대한 사용자 경험 부분을 초기화하기 위해서 이러한 이벤트들을 구독합니다. 뷰는 일반적으로 뷰 모델에 의해서 제공되는 상호작용 요청 오브젝트에 바인딩되는 비헤이비어에 상호작용데 대한 사용자 경험을 캡슐화합니다. 다음은 이를 설명합니다.


사용자와의 상호작용을 위해서 상호작용 요청 오브젝트 사용하기



이 접근법은 단순한, 그리고 여전히 유연한 메커니즘을 제공하는데, 이는 뷰 모델과 뷰 사이의 명확한 관심사 분리를 보존합니다 - 이는 뷰 모델이 응용프로그램의 프리젠테이션 로직과 요청되는 사용자 상호작용들을 캡슐화하도록 해 주는 반면, 뷰가 상호작용의 가시적 관점을 완전하게 캡슐화할 수 있도록 해 줍니다. 뷰를 통해 기대한 사용자와의 상호작용을 포함하는, 뷰 모델의 구현이 쉽게 테스트될 수 있습니다. 그리고 UI 디자이너는, 상호작용을 위해 다양한 사용자 경험을 캡슐화하는 다양한 비헤이비어의 사용을 통해, 상호작용을 구현하는 방식을 선택하는데 있어 많은 유연성을 가지게 됩니다.


이 접근법은 MVVM 패턴을 사용해서 일관성을 가지는데, 뷰가 뷰 모델상에서 관찰하고 있는 상태 변경들을 반영할 수 있게 해 주며, 둘 사이의 데이터 통신을 위해서 양방향 데이터 바인딩을 사용합니다. 상호작용 요청 오브젝트 내의 상호작용에 대한 비가시적 요소들에 대한 캡슐화, 그리고 상호작용의 가시적 요소들을 관리하기 위해 관련 비헤비어를 사용하는 것은, 커맨드 오브젝트와 커맨드 비헤이비어가 사용되는 방식과 매우 유사합니다.


이 접근법은 프리즘에 의해 가깝게 채택됩니다. 프리즘 라이브러리는 IInteractionRequest 인터페이스와 InteractionRequest<T> 클래스를 통해서 이 패턴을 직접적으로 지원합니다. 뷰의 비헤이비어들은 이 인터페이스에 바인딩되며, 그것이 노출하는 이벤트를 구독합니다. InteractionRequeset<T> 클래스는 IInteractionRequest 인터페이스를 구현하고 뷰 모델이 상호작용을 초기화하고 요청을 위한 칸텍스트를 지정하고, 선택적으로 콜백 대리자를 지정하는 것을 허용하기 위해서 두 개의 Raise 메서드를 정의합니다.



뷰 모델에서 상호작용 요청들을 초기화하기


InteractionRequest<T> 클래스는 상호작용 요청 동안에 뷰와 뷰 모델의 상호작용을 조직합니다. Raise 메서드는 뷰 모델이 상호작용을 초기화하고, ( T 타입의 ) 칸텍스트 오브젝트를 지정하고, 상호작용이 완료되었을 때 호출될 콜백 메서드를 지정하는 것을 허용합니다. 칸텍스트 오브젝트는 뷰 모델이 사용자와의 상호작용 동안에 사용될 수 있는 데이터와 상태를 뷰에 넘기는 것을 허용합니다. 만약 콜백 메서드가 정의되어 있다면, 칸텍스트 오브젝트는 뷰 모델에 다시 넘겨질 것입니다; 이는 상호작용 동안에 사용자가 만든 어떤 변경이 뷰 모델로 다시 넘어 가는 것을 허용합니다.



프리즘은 미리 정의된 칸텍스트 클래스를 제공하는데, 이는 일반적인 상호작용 요청 시나리오들을 지원합니다. Notification 클래스는 모든 칸텍스트 오브젝트들을 위한 기저 클래스입니다. 이는 응용프로그램 내의 중요한 이벤트를 사용자에게 통지하기 위해서 상호작용 요청이 사용될 때 사용됩니다. 이는 두 개의 프라퍼티들을 제공합니다 - Title Content - 이것은 사용자에게 출력될 것입니다. 일반적으로 통지들은 단방향이며, 그래서 사용자가 이 값들을 상호작용하는 도중에 변경하는 것은 기대되지 않습니다.


Confirmation 클래스는 Notification 클래스를 상속하고, 세 번째 프라퍼티를 추가합니다 - Confirmed - 이것은 사용자가 확인을 누르거나 작업을 거부했음을 표시하기 위해서 사용됩니다. Confirmation 클래스는 사용자로부터 yes/no 응답을 획득하기 원할 때 MessageBox 스타일 상호작용을 구현하기 위해서 사용됩니다. 당신은 Notification 클래스를 상속하는 커스텀 칸텍스트 클래스를 정의해서 당신이 상호작용을 지원하는데 필요로 하는 데이터와 상태를 캡슐화할 수 있습니다.


InteractionRequest<T> 클래스를 사용하기 위해, 뷰 모델 클래스는 InteractionRequest<T> 클래스의 인스턴스를 생성하고, 뷰가 그것에 데이터 바인딩되는 것을 허용하기 위한 읽기 전용 프라퍼티를 정의할 것입니다. 뷰 모델이 요청을 초기화하고자 할 때, 그것은 Raise 메서드를 호출하는데, 칸텍스트 오브젝트를 넘기고, 선택적으로 콜백 대리자를 넘길 것입니다.



MVVM Reference Implementation( MVVM RI ) 는 조사( survey ) 응용프로그램에서 뷰와 뷰 모델 간의 사용자 상호작용을 구현하기 위해서 IInteractionRequest 인터페이스와 InteractionRequest<T> 클래스가 사용되는 방법에 대해 설명합니다( QuestionnaireViewMode.cs 를 참조하십시오 ).



상호작용 사용자 경험을 구현하기 위해서 비헤이비어들을 사용하기


상호작용 요청 오브젝트는 논리적인 상호작용을 표현하기 때문에, 상호작용을 위한 정확한 사용자 경험은 뷰에 정의됩니다. 상호작용을 위한 사용자 경험을 캡슐화하기 위해서 보통 비헤이비어가 사용됩니다; 이는 UI 디자이너가 적절한 비헤이비어를 선택하고 그것을 뷰 모델의 상호작용 요청 오브젝트에 바인딩할 수 있도록 해 줍니다.


뷰는 반드시 상호작용 요청 이벤트를 검출할 수 잇도록 설정되어야만 하고, 그리고 나서 그 요청을 위한 적절한 가시적 출력을 제출할 수 있도록 설정되어야만 합니다. Microsoft Expression Blend Behavior Framework 은 트릭거와 액션의 개념을 지원합니다. 트리거는 특정 이벤트가 발생할 때마다 액션을 초기화하기 위해서 사용됩니다.


 Expression Blend 에 의해서 제공되는 표준 EventTrigger 는, 뷰 모델에 의해서 노출된 상호작용 요청 오브젝트들에 바인딩됨으로써, 상호작용 요청 이벤트를 모니터링합니다. 그러나 프리즘 라이브러리는 커스텀 EventTrigger 를 정의하느데, InteractionRequestTrigger 라는 이름을 가지고 있고, 그것은 IInteractionRequest 인터페이스에 대한 적절한 Raised 이벤트에 자동적으로 연결됩니다. 이는 필요한 XAML 의 양을 줄여 주고, 잘못된 이벤트 이름이 부주의하게 들어가는 경우를 줄여 줍니다.


이벤트가 발생하고 나면, InteractionRequestTrigger 가 특정 액션을 실행합니다. 실버라이트에 대해, 프리즘 라이브러리는 PopupChildWindowAction 클래스를 제공하는데, 이는 사용자에게 팝업 윈도우를 보여 줍니다. 자식 윈도우가 출력될 때, 그것의 데이터 칸텍스트는 상호작용 요청에 대한 칸텍스트 파라미터로 설정됩니다. PopupChildWindowAction ContentTemplate 프라퍼티를 사용하면, 칸텍스트 오브젝트의 Content 프라퍼티를 위해 사용되는 UI 레이아웃을 정의하기 위한 데이터 템플릿을 지정할 수 있습니다.


팝업 윈도우의 타이틀은 칸텍스트 오브젝트의 Title 프라퍼티에 바인딩됩니다.


노트:

기본적으로, PopupChildWindowAction 클래스에 의해 출력되는 특정 타입의 팝업 윈도우는 칸텍스트 오브젝트의 타입에 의존합니다. Notification 칸텍스트 오브젝트에 대해서는 NotificationChildWindow  가 출력되는 반면에, Confirm 칸텍스트 오브젝트에 대해서는 ConfirmationChildWindow 가 출력됩니다. NotificationChildWindow 는 간단한 팝업 윈도우를 출력해 통지를 보여 주는 반면에, ConfirmationChildWindow OK Cancel 버튼을 가지고 있어서 사용자의 응답을 캡쳐합니다. 당신은 이 비헤이비어를 재정의할 수 있는데, 이는 PopupChildWindowAction 클래스의 ChildWindow 프라퍼티를 사용하여 팝업 윈도우를 지정합으로써 수행됩니다.


다음의 예제는 InteractionRequestTrigger PopupChildWindowAction 을 사용해서 MVVM RI 에서 사용자를 위한 확인용 팝업 윈도우를 출력하는 방법을 보여 줍니다.



노트:

ContentTemplate 프라퍼티를 사용해서 지정되는 데이터 템플릿은 칸텍스트 오브젝트의 Content 프라퍼티를 위한 UI 레이아웃을 정의합니다. 앞의 코드에서, Content 프라퍼티는 문자열이며, 그래서 TextBlock Content 프라퍼티 자체에 바인딩됩니다.


사용자가 팝업 윈도우와 상호작용할 때, 칸텍스트 오브젝트는 팝업 윈도우에서 정의된 바인딩이나 칸텍스트 오브젝트의 Context 프라퍼티를 출력하기 위해서 사용된 데이터 템플릿에 따라 갱신됩니다. 사용자가 팝업 윈도우를 닫은 후에는, 콜백 베서드를 통해서 칸텍스트 오브젝트가 갱신된 값과 함께 뷰 모델에 다시 넘겨 집니다. MVVM RI 에서 사용된 확인 예제에서, 기본 확인 뷰는, OK 버튼이 클릭될 때, 제공된 Confirmation 오브젝트의 Confirmed 프라퍼티를 true 로 설정할 책임이 있습니다.


다른 상호작용 메커니즘을 정의하기 위해서 다양한 트리거들과 액션들이 정의될 수 있습니다. 프리즘의 InteractionRequestTrigger PopupChildWindowAction 클래스에 대한 구현은 당신만의 트리거들이나 액션들을 개발하기 위한 기반으로 사용될 수 있습니다.



진보된 생성 및 엮기( wire-up )



MVVM 패턴을 성공적으로 구현하기 위해서는, 뷰, 모델, 뷰 모델의 책임을 완전하게 이해해야 할 필요가 있습니다. 그래야 올바른 클래스들로 응용프로그램 코드를 구현할 수 있습니다. ( 데이터 바인딩, 커맨드, 상호작용 요청 등을 통해 ) 이러한 클래스들이 상호작용할 수 있도록 만들기 위해 올바른 패턴들을 구현하는 것도 중요한 요구사항입니다. 마지막 단계는 뷰, 뷰 모델, 모델을 런타임에 인스턴스화하고 서로 연관시키는 방법을 고려하는 것입니다.


만약 당신이 응용프로그램에서 종속성 주입 컨테이너를 사용하고 있다면, 이 단계를 관리하기 위한 적절한 전략을 선택하는 것이 특히 중요합니다. MEF 와 유니티는 모두 뷰, 뷰 모델, 모델 사이의 종속성을 지정하기 위한 기능과 런타임에 컨테이너에 의해 그것들을 수행하도록 하는 기능을 제공합니다.


일반적으로, 당ㅅ니은 뷰 모델을 뷰에 대한 의존성으로 정의할 수 있습니다. 그래서 ( 컨테이너를 사용해 ) 뷰가 생성될 때, 그것은 자동적으로 요청된 뷰 모델을 인스턴스화합니다. 다음으로, 뷰 모델이 의존하는 모든 컴포넌트들과 서비스들도 컨테이너에 의해 인스턴스화될 것입니다. 뷰 모델이 성공적으로 생성된 후에는, 뷰가 그것을 자신의 데이터 칸텍스트로 설정하게 됩니다.



MEF 를 사용하여 뷰 와 뷰 모델을 생성하기


MEF 를 사용하면, 당신은 import 애트리뷰트를 사용하여 뷰 모델에 대한 뷰의 의존성을 지정할 수 있습니다. 그리고 export 애트리뷰트를 통해서 인스턴스화될 concrete 뷰 모델 타입을 지정할 수 있습니다. 당신은 프라퍼티를 통해서 뷰에 뷰 모델을 임포트하거나, 뷰 모델을 생성자의 인자로 넘길 수 있습니다.


예를 들어, MVVM RI 뷰에서 Questionnaire 는 뷰 모델을 위한 쓰기 전용 프라퍼티를 선언하며, import 애트리뷰트를 지정합니다. 뷰가 인스턴스화될 때, MEF 는 익스포트된 적절한 뷰 모델의 인스턴스를 생성하고, 프라퍼티 값을 설정합니다. 이 프러퍼티 세터는 뷰 모델을 뷰의 데이터 칸텍스트로 할당합니다. 아래에 그 예가 나와 있습니다.



뷰 모델은 아래와 같이 정의되고 익스포트됩니다.



노트:

MEF 와 유니티에서는 모두 프라퍼티 주입과 생성자 주입을 사용할 수 있습니다; 그러나 당신은 프라퍼티 주입이 더 단순하다는 것을 알게 될 것입니다. 왜냐하면 두 개의 생성자를 유지할 필요가 없기 때문입니다. Visual Studio 나 Expression Blend 와 같은 디자인-타임 툴은 컨틀롤들을 디자이너에 출력하기 위해서 컨트롤들이 파라미터 없는 기본 생성자를 가질 것을 요구합니다. 당신이 정의하는 부가적인 생성자들은 기본 생성자가 호출되는 것을 보장해야만 합니다. 그래야지 뷰가 InitializeComponent 메서드를 통해 적절하게 초기화될 수 있습니다.



유니티를 사용하여 뷰와 뷰 모델을 생성하기


종속성 주입 컨테이너로 유니티를 사용하는 것은 MEF 를 사용하는 것과 유사하며, 프라퍼티-기반 주입과 생성자-기반 주입이 모두 지원됩니다. 기본적인 차이는 보통 타입들이 런타임에 묵시적으로 검색되지 않는다는 것입니다; 그 대신에, 그것들은 컨테이너에 등록되어야만 합니다.


일반적으로 당신은 뷰 모델의 인터페이스를 정의하므로, 뷰 모델의 특정 concrete 타입이 뷰와 디커플링될 수 있습니다. 예를 들어, 뷰는 뷰 모델에 대한 자신의 의존성을 아래와 같이 생성자 인자를 통해서 정의할 수 있습니다.



노트:

파라미터 없는 기본 생성자는 뷰가 Visual Studio 나 Expression Blend 와 같은 디자인 타임 툴에서 작동할 수 있도록 허용하기 위해 필요합니다.


대안적으로, 당신은 뷰의 쓰기 전용 뷰 모델 프라퍼티를 아래와 같이 정의할수 있습니다. 유니티는 요청된 뷰를 인스턴스화할 것이고, 뷰가 인스턴스화된 이후에 프라퍼티 세터를 호출할 것입니다.



뷰 모델 타입은 유니티 컨테이너에 아래와 같이 등록됩니다.



그리고 나서 뷰는 컨테이너를 통해서 아래와 같이 초기화됩니다.




외부( external ) 클래스를 사용하여 뷰와 뷰 모델을 생성하기


보통, 당신은 뷰 및 뷰 모델의 인스턴스화를 조직하기 위해서 컨트롤러나 서비스를 정의하는 것이 유용합을 발견하게 될 것입니다. 이 접근법은 MEF 나 유니티와 같은 의존성 주입 컨테이너와 함께 사용될 수 있거나, 뷰가 명시적으로 자신이 요구하는 뷰 모델을 생성할 때 사용될 수 있습니다.


이 접근법은 응용프로그램에서 네비게이션을 구현할 때 특히 유용합니다. 이 경우에, 컨트롤러는 placeholder 컨트롤이나 UI 의 리전( region )과 연관되며, 그것은 뷰의 생성과 배치를 그 placeholder 나 리전으로 조직합니다.


예를 들어, MVVM RI 는 컨테이너를 사용하여 뷰를 생성하는 서비스 클래스를 사용하고, 그것들을 메인 페이지에서 보여 줍니다. 이 예제에서, 뷰는 뷰 이름에 의해 지정됩니다. 네비게이션은 아래와 같이 UI 서비스의 ShowView 메서드에 대한 호출을 통해 인스턴스화됩니다.



UI 서비스는 응용프로그램 UI 에 있는 placeholder 컨트롤에 연관됩니다; 그것은 요청된 뷰의 생성을 캡슐화하고, UI 에서의 외형을 조직화합니다. UIService 의 ShowView 는 아래와 같이 컨테이너를 통해서 뷰의 인스턴스를 생성하고( 그래서 그것의 뷰 모델과 다른 의존성들이 수행될 수 있다 ) 그것을 적절한 위치에 출력합니다.



노트:

프리즘은 리전을 사용하여 네비게이션을 위한 확장적인 지원을 제공하니다. 리전 네비게이션은 앞의 접근법과 매우 유사한 메커니즘을 사용하는데, 리전 관리자가 지정된 영역 내에 뷰의 인스턴스화와 배치를 조직화할 책임이 있다는 차이가 있습니다. 더 많은 정보를 원한다면, 챕터 8 "네비게이션" 의 "뷰-기반 네비게이션" 섹션을 참조하십시오.



MVVM 응용프로그램들을 테스트하기



중략...



More Information



For more information about the logical tree, see "Trees in WPF" on MSDN:
http://msdn.microsoft.com/en-us/library/ms753391.aspx


For more information about attached properties, see "Attached Properties Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/cc265152(VS.95).aspx


For more information about MEF, see "Managed Extensibility Framework Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/dd460648.aspx.


For more information about Unity, see "Unity Application Block" on MSDN:
http://www.msdn.com/unity.


For more information about DelegateCommand, see Chapter 5, "Implementing the MVVM Pattern."


For more information about using Microsoft Expression Blend behaviors, see "Working with built-in behaviors" on MSDN:

http://msdn.microsoft.com/en-us/library/ff724013(v=Expression.40).aspx.


For more information about creating custom behaviors with Microsoft Expression Blend, see "Creating Custom Behaviors" on MSDN:
http://msdn.microsoft.com/en-us/library/ff724708(v=Expression.40).aspx.


For more information about creating custom triggers and actions with Microsoft Expression Blend, see "Creating Custom Triggers and Actions" on MSDN:
http://msdn.microsoft.com/en-us/library/ff724707(v=Expression.40).aspx.


For more information about using the dispatcher in WPF and Silverlight, see "Threading Model" and "The Dispatcher Class" on MSDN:
http://msdn.microsoft.com/en-us/library/ms741870.aspx
http://msdn.microsoft.com/en-us/library/ms615907(v=VS.95).aspx.


For more information about unit testing in Silverlight, see "Unit Testing with Silverlight 2":
http://www.jeff.wilcox.name/2008/03/silverlight2-unit-testing/.


For more information about region navigation, see the section, "View-Based Navigation" in Chapter 8, "Navigation."


For more information about the Event-based Asynchronous pattern, see "Event-based Asynchronous Pattern Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/wewwczdw.aspx


For more information about the IAsyncResult design pattern, see "Asynchronous Programming Overview" on MSDN:
http://msdn.microsoft.com/en-us/library/ms228963.aspx


'Programming > Prism4.1' 카테고리의 다른 글

MVVM 패턴 구현하기  (0) 2014.09.16
모듈러 응용프로그램 개발  (0) 2014.09.13
컴포넌트 간의 종속성 관리하기  (0) 2014.09.10
프리즘 응용프로그램 초기화하기  (0) 2014.08.24
왜 프리즘을 사용하는가?  (0) 2014.08.19
소개  (0) 2014.08.18
프리즘 4.1 설치  (0) 2014.08.14

프리즘 4.1 도움말 번역입니다.



MVVM 패턴 구현하기



모델-뷰-뷰모델( MVVM ) 패턴은 응용프로그램의 비즈니스 로직과 프리젠테이션 로직을 그것의 유저 인터페이스( UI )와 명확하게 분리하는 데 도움을 줍니다. 응용프로그램 로직과 UI 를 깔끔하게 분리하는 상태를 유지하는 것은 다양한 개발 및 설계 이슈들을 제시하는데 도움을 주며, 당신의 응용프로그래이 더 쉽게 테스트되고, 유지보수되고, 진화될 수 있도록 만들어 줄 수 있습니다. 또한 코드 재사용 가능성을 많이 증대시켜 줄 수 있으며, 개발자와 UI 디자이너가 응용프로그램에서 그들의 주요 관심사를 개발하는데 있어 더 쉽게 협력할 수 있도록 해 줍니다.


MVVM 패턴을 사용하면, 응용프로그램의 UI 와 기저에 깔린 프리젠테이션 로직 및 비즈니스 로직이 세 개의 분리된 클래스로 나뉩니다: 뷰는 UI 와 UI 로직을 캡슐화합니다; 뷰모델은 프리젠테이션 로직과 상태를 캡슐화합니다. 모델은 응용프로그램의 비즈니스 로직과 데이터를 캡슐화합니다.


프리즘은 실버라이트와 WPF 응용프로그램에서 MVVM 패턴을 구현하는 방법을 보여주는 샘플들과 참조 구현들을 포함하고 있습니다. 또한 프리즘 라이브러리는 당신의 응용프로그램에서 그 패턴을 구현하는데 도움을 줄 수 있는 기능들도 제공합니다. 이 기능들은 MVVM 패턴을 구현하기 위한 가장 공통적인 경험들을 포함하고 있으며, testability 를 지원하도록 설계되었으며, 익스프레션 블렌드와 비주얼 스튜디오에서 잘 동작하도록 설계되었습니다.


이 챕터는 MVVM 패턴에 대한 개요를 제공하며, 그것의 기본적인 특징들을 구현하는 방법에 대해 기술합니다. 챕터 6 은 프리즘 라이브러리를 사용해 더욱 진보된 MVVM 시나리오를 구현하는 방법을 기술합니다.



클래스의 책임과 특징들



MVVM 패턴은 프리젠테이션 모델( PM ) 패턴과 가까운 변종이며, 데이터 바인딩, 데이터 템플릿, 커맨드, 비헤이비어( behavior ) WPF 와 실버라이트의 핵심 기능들의 일부를 활용하기 위해서 최적화되어 있습니다.


MVVM 패턴에서, 뷰는 UI 와 UI 로직을 캡슐화하니다. 뷰 모델은 프리젠테이션 로직과 상태를 캡슐화합니다. 그리고 모델은 비즈니스 로직과 데이터를 캡슐화하니다. 뷰는 뷰 모델과 데이터 바인딩, 커맨드, 변경 통지 이벤트 등을 통해 상호작용합니다. 뷰 모델은 모델에 대한 업데이트를 질의하고 관찰하고 조직화하는데, 뷰의 출력에 필요한 형태로 데이터를 변환하고 유효화하고 그룹화합니다.


다음 그림은 세 개의 MVVM 클래스들과 그것들의 상호작용을 보여 줍니다.


MVVM 클래스들과 그것들의 상호작용




모든 개별 프리젠테이션 패턴들과 유사하게, MVVM 패턴을 사용하는 것의 핵심은 효율적으로 응용프로그램의 코드를 올바른 클래스들에 생성하는 적절한 방식을 이해하는데 달려 있으며, 이 클래스들이 다양한 시나리오에서 상호작용하는 방식을 이해하는 것에 달려 있습니다. 다음 섹션들에서는 MVVM 패턴의 각 클래스들의 책이과 특징들에 대해 기술합니다.



뷰 클래스


뷰의 책임은 스크린에서 사용자가 보게 되는 외형과 구조를 정의하는 것입니다. 이상적으로 볼 때, 뷰의 코드-비하인드는 단지 InitializeComponent 메서드를 호출하는 생성자만을 포함하는 것입니다. 어떤 경우에, 코드-비하인드는 복잡한 애니메이션과 같이 XAML 에서 표현하기 어렵거나 비효율적인 비주얼 비헤이비어를 구현하는 UI 로직 코드를 포함할 수 있습니다. 혹은 뷰의 일부인 비주얼 엘리번트들이 직접적으로 조작될 필요가 있는 코드가 있을 때 UI 로직 코드를 포함할 수 있습니다. 단위 테스트를 필요로 하는 뷰에 로직 코드를 배치해서는 안 됩니다. 일반적으로, 뷰의 코드-비하인드에 있는 로직 코드는 UI 오토메이션 테스팅 접근법을 통해 테스트될 수 있을 것입니다.


실버라이트와 WPF 에서, 뷰의 데이터 바인딩 표현식은 그것의 데이터 칸텍스트에 대해 평가됩니다. MVVM 에서, 뷰의 데이터 칸텍스트는 뷰 모델로 설정됩니다. 뷰 모델은 뷰 가 바인딩할 수 있는 프라퍼티나 커맨드를 구현합니다. 그리고 변경 통지 이벤트를 통해 어떠한 상태 변경을 뷰에 통지합니다. 일반적으로 뷰와 그것의 뷰 모델은 1 대 1 관계를 가집니다.


보통, 뷰는 Control 상속 클래스이거나 UserControl 상속 클래스입니다. 그러나, 어떤 경우에는, 뷰가 데이터 템플릿을 통해 표현될 수도 있습니다. 그것은 오브젝트가 출력될 때 오브젝트를 가시적으로 표현하기 위해서 사용되는 UI 엘리먼트들을 지정합니다. 데이터 템플릿을 사용하면, 비주얼 디자이너가 뷰 모델이 렌더링될 방식을 쉽게 결정하거나, 기반 오브젝트 자체를 변경하지 않고도 그것의 가시적인 표현이나 그것을 출력하기 위해서 사용될 컨트롤의 동작을 변경할 수 있습니다.


데이터 템플릿은 코드-비하인드를 가지지 않는 뷰라고 생각할 수 있습니다. 그것들은 UI 에서 오브젝트가 출력되기를 요청받을 때마다 특정 뷰 모델 타입을 바인딩하도록 설계되어 있습니다. 런타임에, 그 뷰는 데이터 템플릿에 정의되어 있는대로 자동적으로 인스턴스화될 것이며, 그것의 데이터 컨텍스트가 관련 뷰 모델로 설정될 것입니다.


WPF 에서, 당신은 데이터 템플릿을 응용프로그램 레벨에서 뷰 모델 타입과 연관시킬 수 있습니다. 그러면 WPF 는 오브젝트가 UI 에서 출력될 때마다 특정 타입의 뷰 모델 오브젝트들에 대해 자동적으로 데이터 템플릿을 적용하게 될 것입니다. 이는 묵시적 데이터 템플리팅이라고 알려져 있습니다. 실버라이트에서, 당신은 오브젝트를 출력할 컨트롤 안에 뷰 모델 오브젝트를 위한 데이터 템플릿을 명식적으로 지정해야만 합니다. 두 경우 모두, 데이터 템플릿은 그것을 사용하는 컨트롤 내부에 인라인으로 정의되거나, 부모 뷰의 외부의 리소스 딕셔너리에 정의되거나, 뷰의 리소스 딕셔너리에 선언적으로 병합될 수 있습니다.


요약하면, 뷰는 다음과 같은 핵심 특징들을 가집니다:

  • 뷰는 윈도우, 페이지, 유저 컨트롤, 데이터 템플릿과 같은 비주얼 엘리먼트입니다. 뷰는 뷰에 포함된 컨트롤들을 정의하고, 그것들의 비주얼 레이아웃과 스타일링을 정의합니다.
  • 뷰는 DataContext 프라퍼티를 통해 뷰 모델을 참조합니다. 뷰 내부의 컨트롤들은 그 프라퍼티들에 바인딩된 데이터이며, 뷰 모델에 의해 노출된 커맨드들입니다.
  • 뷰는 뷰와 뷰 모델 간의 데이터 바인딩 동작을 커스터마이즈할 수 있습니다. 예를 들어, 뷰는 value converter 를 사용해 데이터를 UI 에 출력할 형식으로 변환하거나, 유효화 규칙을 사용하여 사용자에게 입력 데이터에 대한 부가적인 유효성 검사를 제공할 수도 있습니다.
  • 뷰는 뷰 모델에서의 상태 변화나 UI 와 사용자의 인터랙션에 의해 발생된 애니메이션이나 트랜지션과 같은 UI 비주얼 비헤이비어를 정의하고 다룹니다.
  • 뷰의 코드-비하인드는 XAML 에서 표현하기 어려운 비주얼 비헤이비어를 구현하기 위해서 UI 로직을 정의하거나, 뷰에서 정의된 특정 UI 컨트롤들에 대한 직접적인 참조를 요청할 수도 있습니다.


뷰 모델 클래스


MVVM 패턴에서 뷰 모델은 프리젠테이션 로직과 뷰를 위한 데이터를 캡슐화합니다. 그것은 뷰에 대한 직접적인 참조나 뷰의 특정 구현이나 타입에 대한 정보를 가지고 있지 않습니다. 뷰 모델은 뷰가 바인드할 수 있는 프라퍼티와 커맨드를 구현합니다. 그리고 변경 통지 이벤트를 통해 어떠한 상태 변경을 뷰에 통지합니다. 뷰 모델이 제공하는 프라퍼티와 커맨드는 UI 에 의해 제공되는 기능을 정의하지만, 뷰는 그 기능이 렌더링되는 방식을 결정합니다.


뷰 모델은 뷰와 그것이 요구하는 모델 클래스들 간의 상호작용을 조직할 책임이 있습니다. 일반적으로 뷰 모델과 모델 클래스들 간에는 1 대 다 관계가 존재합니다. 뷰 모델은 뷰에 모델 클래스를 직접적으로 노출하는 것을 선택할 것이며, 그래서 뷰 내의 컨트롤들은 그것들에 직접적으로 데이터를 바인딩할 것입니다. 이 경우, 모델 클래스들은 데이터 바인딩과 적절한 변경 통지 이벤트들을 지원하도록 설계될 필요가 있을 것입니다. 이 시나리오에 대한 더 많은 정보를 원한다면, 이 토픽의 뒤에 나오는 데이터 바인딩 섹션을 참조하십시오.


뷰 모델은 모델 데이터를 변환하거나 조작하므로, 그것은 뷰에 의해 쉽게 소비될 수 있습니다. 뷰 모델은 뷰를 특별하게 지원하기 위해서 부가적인 프라퍼티들을 정의할 수 있습니다; 이 프라퍼티들은 보통은 모델의 일부가 아닙니다( 혹은 모델에 추가될 수 없습니다 ). 예를 들어, 뷰 모델은 두 필드의 값을 뷰에 제출하기 쉽게 만들기 위해서 결합하거나, 최대 길이를 가진 필드의 인풋을 위해 남은 문자열 개수를 계산할 수도 있습니다. 또한 뷰 모델은 데이터의 일관성을 보장하기 위해서 데이터 유효성 로직을 구현할 수도 있습니다.


그리고 뷰 모델은 뷰가 UI 의 가시적인 변경을 제공하는데 사용할 수 있는 논리적 상태들을 정의할 수도 있습니다. 뷰는 뷰 모델의 상태를 반영하는 레이아웃이나 스타일링을 정의할 것입니다. 예를 들어, 뷰 모델은 데이터가 비동기적으로 웹 서비스에 대해 제출되고 있음을 지시하는 상태를 정의할 수 있습니다. 뷰는 이 상태가 유지되는 동안에 사용자에게 가시적 피드백을 제공하기 위해서 애니메이션을 출력할 수 있습니다.


일반적으로, 뷰 모델은 UI 에서 표현될 수 있고 사용자가 호출할 수 있는 커맨드나 액션을 정의할 것입니다. 일반적인 예는 사용자가 데이터를 웹 서비스나 데이터 저장소로 제출할 수 있도록 해 주는 Submit 커맨드를 뷰 모델이 제공할 때입니다. 뷰는 그 커맨드를 버튼으로 표현하는 것을 선택할 것이며, 사용자는 데이터를 제출하기 위해서 그 버튼을 클릭할 수 있습니다. 일반적으로, 커맨드가 이용 가능한 상태가 아닐 때, 그것과 관련된 UI 표현은 비활성화됩니다. 커맨드들은 사용자 액션을 캡슐화하고, 그것을 UI 에서의 가시적인 표현과 명확하게 분리하는 방법을 제공합니다.


요약하면, 뷰 모델은 다음과 같은 핵심 특징들을 가집니다:

  • 뷰 모델은 비가시적인 크래스이며, WPF 나 실버라이트 기저 클래스를 상속하지 않습니다. 그것은 유스 케이스나 응용프로그램에서의 사용자 태스크를 지원하기 위해서 요구되는 프리젠테이션 로직을 캡슐화합니다. 뷰 모델은 뷰와 모델과는 별개로 테스트 가능합니다.
  • 뷰 모델은 일반적으로 뷰에 대한 직접적인 참조를 하지 않습니다. 그것은 뷰가 바인드할 수 잇는 프라퍼티와 커맨드들을 제공합니다. 그것은 INotifyPropertyChanged INotifyCollectionChanged 인터페이스를 통한 변경 통지 이벤트를 통해 뷰에 어떠한 상태 변경들을 통지합니다.
  • 뷰 모델은 뷰와 모델의 상호작용을 조직화합니다. 그것은 데이터를 변환하고 조작해, 데이터가 뷰에 의해 소비되기 쉬운 형태로 만들어질 수 있으며, 모델 상에서 제출되지 않는 부가적인 프라퍼티들을 구현할 수도 있습니다. 또한 IDataErrorInfo INotifyDataErrorInfo 인터페이스들을 통해 데이터 유효성을 구현할 수도 있습니다.
  • 뷰 모델은 뷰가 가시적으로 사용자에게 표현할 수 있는 논리적 상태를 정의할 수 있습니다.
노트:
뷰 혹은 뷰 모델?
특정 기능이 어디에서 구현되어야 하는지는 불명확합니다. 범용적인 경험 법칙에 따르면: 스크린상의 UI 의 특정 가시적 외형과 관련된 것들과 나중에 다시 스타일링될 수 있는( 심지어 당신이 현재는 다시 스타일링할 것을 계획하고 있지 않더라도 ) 것들은 뷰로 들어 가야 합니다; 응용프로그램의 논리적 행위에 있어 중요한 것들은 뷰 모델로 들어 가야 합니다. 부가적으로, 뷰 모델은 뷰의 특정 비주얼 엘리먼트에 대한 명시적인 정보를 가지고 있지 않기 때문에, 뷰에서 비주얼 엘리먼트들을 프로그램적인 방법으로 다루기 위한 코드는 뷰의 코드 비하인드에 존재하거나 비헤이비어에 캡슐화되어야 합니다. 이와 유사하게, 데이터 바인딩을 통해 뷰에서 출력될 데이터 아이템들을 조작하고 획득하는 코드는 뷰 모델에 존재해야 합니다.
예를 들어, 리스트 박스에서 선택된 아이템에 대한 하이라이트 컬러는 뷰에 정의되어야 하지만, 출력할 아이템들의 리스트와 선택된 아이템 자체에 대한 참조는 뷰 모델에 의해 정의되어야 합니다.



모델 클래스


MVVM 패턴에서 모델은 비즈니스 로직과 데이터를 캡슐화합니다. 비즈니스 로직은 응용프로그램 로직으로 정의되는데, 이는 응용프로그램 데이터에 대한 획득과 관리와 관련이 있습니다. 그리고 비즈니스 룰이 데이터의 일관성과 유효성을 보장하도록 하는 것을 내포합니다. 재사용성의 기회를 최대화하기 위해서, 모델은 어떠한 유스 케이스-지정 혹은 유저 태스크-지정 행위나 응용프로그램 로직을 포함해서는 안 됩니다.


일반적으로, 모델은 응용프로그램을 위한 클라이언트측 도메인 모델을 표현합니다. 그것은 응용프로그램의 데이터 모델과 비즈니스 로직 및 유효성 로직을 지원하는 것에 기반한 데이터 구조를 정의할 수 있습니다.이 모델은 데이터 접근이나 캐싱을 지원하기 위한 코드를 포함할 수도 있는데, 이는 보통 이를 위해 사용되는 분리된 데이터 저장소나 서비스를 통해서 이루어집니다. 보통, 모델과 데이터 접근 레이어는 ADO.NET Entity Framework, WCF Data Services, WCF RIA Services 와 같은 데이터 접근 혹은 서비스 전략의 일부로서 생성됩니다.


일반적으로, 모델은 뷰에 바인딩되기 쉽도록 해 주는 기능들을 구현합니다. 이는 보통 그것이 INotifyPropertyChanged INotifyCollectionChanged 인터페이스들을 통해서 프라퍼티 변경 통지나 컬렉션 변경 통지를 지원한다는 것을 의미합니다. 오브젝트 컬렉션을 표현하는 모델들 클래스들은 보통 ObservableCollection<T> 클래스를 상속하는데, 이 클래스는 INotifyCollectionChanged 인터페이스에 대한 구현을 제공합니다.


모델은 IDataErrorInfo( 혹은 INotifyDataErrorInfo ) 인터페이스들을 통해 데이터 유효화나 데이터 리포팅을 지원할 수도 있습니다. 이 인터페이스들은 WPF 나 실버라이트의 데이터 바인딩으로 하여금 값이 변경될 때를 통지받을 수 있도록 해서, UI 가 갱신될 수 있도록 해 줍니다. 또한 그것들은 UI 레이어에서 데이터 유효화나 에러 리포팅을 지원할 수 있도록 합니다.


노트:

당신의 모델 클래스들이 요청된 인터페이스를 구현하지 않는다면?

가끔 당신이 INotifyPropertyChanged, INotifyCollectionChanged, IDataErrorInfo, INotifyDataErrorInfo 인터페이스를 구현하지 않는 모델 오브젝트를 가지고 작업하게 될 경우가 있을 것입니다. 그런 경우, 뷰 모델은 모델 오브젝트들을 래핑할 필요가 있을 것이며, 요청된 프라퍼티들을 뷰에 노출할 필요가 있을 것입니다. 이 프라퍼티들의 값들은 모델 오브젝트들에 의해 직접적으로 제공될 것입니다. 뷰 모델은 그것이 노출하는 프라퍼티들을 위해 요청된 인터페이스들을 구현할 것이며, 그래야지 뷰가 쉽게 그것들을 데이터 바인딩할 수 있을 것입니다.


모델은 다음과 같은 핵심 특징들을 가지고 있습니다:

  • 모델 클래스들은 응용프로그램의 데이터 로직 및 비즈니스 로직을 캡슐화하는 비가시적 클래스들입니다. 그것들은 응용프로그램의 데이터들을 관리하고 요청된 비즈니스 룰과 데이터 유효성 로직을 캡슐화함으로써 그 데이터의 일관성과 유효성을 보장할 책임을 가집니다.
  • 모델 클래스들은 뷰나 뷰 모델에 대한 직접적인 참조를 가지지 않습니다. 그리고 그것들이 구현되는 방식에 대한 종속성을 가지지 않습니다. 
  • 모델 클래스들은 일반적으로 프라퍼티 변경 통지 이벤트 및 컬렉션 변경 통지 이벤트들을 제공하는데, 이는 INotifyPropertyChanged 인터페이스와 INotifyCollectionChanged 인터페이스를 통해 지원됩니다. 이는 그것들이 뷰에 쉽게 데이터 바인딩되도록 해 줍니다. 오브젝트에 대한 컬렉션을 표현하는 모델 클래스들은 ObservableCollection<T> 클래스를 상속합니다.
  • 모델 클래스들은 일반적으로 IDataErrorInfo INotifyDataErrorInfo 인터페이스를 통해 데이터 유효화나 에러 리포팅을 제공합니다.
  • 모델 클래스들은 일반적으로 데이터 접근 및 캐싱을 캡슐화하는 서비스나 저장소와 함께 사용됩니다.


클래스 상호작용들



MVVM 패턴은 응용프로그램의 사용자 인터페이스, 프리젠테이션 로직, 그리고 비즈니스 로직과 데이터를 분리된 클래스로 나눔으로써 그것들 간의 명확한 분리를 제공합니다. 그러므로, 당신이 MVVM 을 구현할 대, 응용프로그램의 코드에서 이전 섹션에서 기술했던 것처럼 정확한 클래스들을 생성하는 것이 중요합니다.

잘 설계된 뷰, 뷰 모델, 모델 클래스들은 코드 및 행위의 정확한 타입만을 생성하는 것은 아닙니다; 그것들은 데이터 바인딩, 커맨드, 데이터 유효성 인터페이스들을 통해 서로 쉽게 상호작용할 수 있도록 설계되기도 합니다.

뷰와 그것의 뷰 모델 간의 상호작용은 아마도 고려해야 할 가장 중요한 것일 것입니다만, 모델과 뷰 모델 간의 상호작용도 중요합니다. 다음 섹션들은 이러한 상호작용들을 위한 다양한 패턴들을 기술하며, 응용프로그램에서 MVVM 패턴을 구현할 때 그것들을 설계하는 방법에 대해서 기술합니다.


데이터 바인딩

데이터 바인딩은 MVVM 패턴에서 매우 중요한 역할을 수행합니다. WPF 와 실버라이트는 모두 강력한 데이터 바인딩 기능을 제공합니다. 당신의 뷰 모델과 당신의 뷰 모델 클래스들은 ( 이상적으로는 ) 데이터 바인딩을 지원해서 그것들이 이러한 기능을의 이점을 취할 수 있도록 설계되어야 합니다. 일반적으로, 이는 그것들은 올바른 인터페이스들을 구현해야만 한다는 것을 의미합니다.

실버라이트와 WPF 의 데이터 바인딩은 다중 데이터 바인딩 모드를 지원합니다. 단방향( one-way ) 데이터 바인딩을 사용하며, UI 컨틀롤들이 뷰 모델에 바인딩될 수 있으며, 그래서 그것들은 디스플레이가 렌더링될 때 기저 데이터의 값을 반영합니다. 양방향( two-way ) 데이터 바인딩은 UI 에서 사용자가 데이터를 수정할 때 데이터를 자동적으로 갱신합니다.

뷰 모델에서 데이터가 변경될 때 UI 가 최신상태를 유지하도록 하기 위해서는, 적절한 변경 통지 인터페이스를 구현해야 합니다. 만약 그것이 데이터 바인딩될 수 있는 프라퍼티들을 정의한다면, 그것은 INotifyPropertyChanged 인터페이스를 구현해야 합니다. 만약 뷰 모델이 컬렉션을 표현한다면, 그것은 INotifyCollectionChanged 인터페이스를 구현하거나, 이 인터페이스에 대한 구현을 제공하는 ObservableCollection<T> 클래스를 상속해야 합니다. 이 인터페이스들은 모두 기저 데이터가 변경될 때 발생하는 이벤트를 정의합니다. 데이터가 바인딩된 컨트롤들은 이벤트가 발생될 때 자동적으로 갱신될 것입니다.

많은 경우에, 뷰 모델은 오브젝트들을 반환하는 프라퍼티들을 정의할 것입니다( 그리고 순서대로 부가적인 오브젝트들을 반환하는 프라퍼티들일 수도 있습니다 ). WPF 와 실버라이트에서 데이터 바인딩은 Path 프라퍼티를 통해서 내부 프라퍼티들에 대한 바인딩을 지원합니다. 그러므로, 뷰의 뷰 모델을 위해 다른 뷰 모델이나 모델에 대한 참조를 반하는 것이 매우 일반적인 일입니다. 뷰에 전근할 수 있는 뷰 모델과 모델은  INotifyPropertyChanged INotifyCollectionChanged 인터페이스를 적절히 구현해야 합니다.


다음 섹션들은 MVVM 에서 데이터 바인딩을 지원하기 위해서 요구되는 인터페이스들을 구현하는 방법에 대해 기술합니다.



INotifyPropertyChanged 구현하기


뷰 모델이나 모델 클래스에서 INofityPropertyChanged 인터페이스를 구현하는 것은, 기저 프라퍼티 값이 변경될 때 그것들이 뷰에 있는 데이터 바인딩된 컨트롤들에 변경 통지들을 제공할 수 있도록 해 줍니다.  이 인터페이스를 구현하는 것은 직관적입니다. 아래에 코드 샘플이 있습니다( MVVM QuickStart 의 Questionnaire 클래스를 참조하십시오 ).



INotifyPropertyChanged 인터페이스를 많은 뷰 모델 클래스들에서 구현하는 것은 반복적이고 에러 가능성이 높습니다. 왜냐하면 이벤트 파라미터에 프라퍼티 이름을 지정할 필요가 있기 때문입니다. 프리즘 라이브러리는 뷰 모델 클래스에서 상속할 수 있는 편리한 기저 클래스를 제공하는데, 이는 INotifyPropertyChanged 인터페이스를 형-안정적인 방식으로 제공합니다. 그 예가 아래에 나와 있습니다.



이를 상속한 뷰 모델은 RaisePropertyChanged 를 프라퍼티 이름을 지정해 호출하거나 프라퍼티를 참조하는 람다 표현식을 사용해서 호출함으로써 프라퍼티 변경 이벤트를 발생시킬 수 있습니다. 그 예가 아래에 나와 있습니다.



노트:

람다 표현식을 사용할 때, 이 방식은 약간의 성능 비용을 포함합니다. 왜냐하면 람다 표현식은 각 호출마다 평가되어야만 하기 때문입니다. 이러한 접근법의 이점은 당신이 프라퍼티의 이름을 변경했더라도 컴파일 타임 타입 안정성 및 리팩토링 지원을 제공한다는 것입니다. 성능 부하가 작고 당신의 응용프로그램에 대해 거의 영향을 주지 않는다고 하지만, 많은 변경 통지들이 발생한다면 이 비용들이 누적됩니다. 이 경우, 람다 메서드를 사용하지 않는 방식을 사용하는 것을 고려해야 합니다.


보통, 당신의 모델이나 뷰 모델은 모델이나 뷰 모델의 다른 프라퍼티들로부터 계산되는 값들을 가진 프라퍼티들을 포함할 것입니다. 프라퍼티들에 대한 변경을 다룰 때, 계산된 프라퍼티들에 대한 통지 이벤트들도 발생될 수 있도록 하십시오.



INotifyCollectionChanged 구현하기


당신의 뷰 모델이나 모델이 아이템들의 집합을 표현하거나 아이템들의 집합을 반환하는 하나 이상의 프라퍼티들을 가지고 있을 수 있습니다. 이러한 경우에, 당신은 ListBox DataGrid 컨트롤과 같은 ItemsControl 에 그 컬렉션들을 출력하기를 원할 것입니다. 컬렉션을 표현하는 뷰 모델이나 컬렉션을 반환하는 프라퍼티들은 ItemSource 프라퍼티를 통해 데이터 바인딩될 수 있습니다.



변경 통지 요청들을 적절하게 지원하기 위해서, 뷰 모델이나 모델은 그것들이 컬렉션을 표현하고 있을 때 INofityCollectionChanged 인터페이스를 구현해야 합니다( 부가적으로 INotifyPropertyChanged 인터페이스를 구현할 수도 있습니다 ). 만약 뷰 모델이나 모델이 컬렉션에 대한 참조를 반환하는 프라퍼티를 정의한다면, 반환되는 컬렉션 클래스는 INotifyCollectionChanged 인터페이스를 구현해야 합니다.


그러나 INofityCollectionChanged 인터페이스를 구현하는 것은 어려운 일이 될 수 있습니다. 왜냐하면 그것은 컬렉션 내에서 아이템들이 추가되거나, 삭제되거나, 변경될 때에 대한 통지를 제공해야 하기 때문입니다. 그 인터페이스를 직접적으로 구현하기보다는, 보통 그것을 이미 구현해 놓은 컬렉션 클래스로부터 상속을 받거나 그 클래스를 사용하는 것이 더 쉽습니다. ObservableCollection<T> 클래스는 이 인터페이스에 대한 구현을 제공하고, 기저 클래스로 사용되거나 아이템의 컬렉션을 표현하는 프라퍼티를 구현하는데도 사용됩니다.


만약 데이터 바인딩을 위해서 뷰에 컬렉션을 제공할 필요는 있지만, 사용자의 선택을 트래킹하거나 컬렉션 내에서의 필터링, 정렬, 그룹화를 지원할 필요가 없다면, 당신은 그냥 단순하게 당신의 뷰 모델상의 프라퍼티가 ObservableCollection<T> 인스턴스를 반환하도록 정의하면 됩니다.



만약 컬렉션 클래스에 대한 참조를 획득한다면( 예를 들어, INotifyCollectionChanged 를 구현하지 않는 다른 컴포넌트나 서비스로부터 참조를 획득한다면 ), 당신은 보통 그 컬렉션을 ObservableCollection<T> 인스턴스의 컬렉션으로 래핑할 수 있으며, 이는 IEnuerable<T>List<T> 파라미터를 취하는 생성자 중의 하나를 사용합니다.



ICollectionView 구현하기


이전 코드 예제들은 뷰에 데이터 바인딩된 컨트롤들을 통해 출력될 수 있는 아이템에 대한 컬렉션들을 반환하는 단순한 뷰 모델 프라퍼티들을 구현하는 방법을 보여 줍니다. ObservableCollection<T> 클래스는 INofityCollectionChanged 인터페이스를 구현하기 때문에, 뷰의 컨트롤들은 자동적으로 갱신되어 아이템들이 추가되거나 삭제될 때마다 컬렉션의 현재 아이템 리스트를 반영할 것입니다.


그런데, 당신은 종종 뷰에 아이템 컬렉션이 출력되는 방식을 좀 더 잘 제어하고 싶다거나 출력된 아이템 컬렉션과 사용자의 상호작용을 트래킹하고 싶다거나, 그러한 동작을 뷰 모델 자체에서 수행하고 싶을 때가 있을 것입니다. 예를 들어, 당신은 아이템 컬렉션이 뷰 모델에서 제공하고 있는 프리젠테이션 로직에 따라 필터링되고 정렬되기를 원하거나, 뷰에서 현재 선택되어 있는 아이템에 대한 트랙을 유지하여 뷰 모델에서 구현된 커맨드들이 현재 선택된 아이템에 대한 동작을 수행할 수 있도록 할 필요가 있을 것입니다.


WPF 와 실버라이트는 ICollectionView 인터페이스를 구현하는 다양한 클래스들을 제공함으로써 이러한 시나리오들을 지원합니다. 이 인터페이스는 컬렉션이 필터링되고, 정렬되고, 그룹화되는 것을 허용하는 프라퍼티들과 메서드들을 지원합니다. 그리고 현재 선택된 아이템들이 트래킹 되고 변경되는 것을 허용합니다. 실버라이트와 WPF 는 모두 이 인터페이스의 구현을 제공합니다 - 실버라이트는 PagedCollectionView 클래스를 제공하고, WPF 는 ListCollectionView 를 제공합니다.


컬렉션 뷰 클래스는 기저 아이템 컬렉션을 래핑하여 그것들이 자동적으로 그것들에 대한 트래킹, 정렬, 필터링, 페이징을 제공할 수 있도록 합니다. 이 클래스들의 인스턴스는 CollectionViewSource 클래스를 사용하여 XAML 안에서 선언적으로 생성되거나 프로그램적으로 생성될 수 있습니다.


노트:

WPF 에서는 컬렉션에 컨트롤이 바인딩될 때마다 자동적으로 기본 컬렉션 뷰가 생성될 것입니다. 실버라이트에서는 바인딩된 컬렉션이 ICollectionViewFactory 인터페이스를 지원할 때만 자동적으로 컬렉션 뷰가 생성될 것입니다.


컬렉션 뷰 클래스는 기저 컬렉션에 대한 중요한 상태 정보에 대한 트랙을 유지하기 위해서 뷰 모델에 의해 사용될 수 있습니다. 반면에 뷰의 UI 와 모델의 기저 데이터 사이에서의 명확한 관심사 분리가 유지됩니다. 실제적으로, CollectionView 들은 특별히 컬렉션들을 지원하기 위해서 설계된 뷰 모델입니다.


그러므로, 당신이 뷰 모델에서 컬렉션의 아이템들에 대한 필터링, 정렬, 그룹화, 선택 트래킹을 구현할 필요가 있다면, 당신의 뷰 모델은 뷰에 노출되는 각 컬렉션에 대해 컬렉션 뷰 클래스의 인스턴스를 생성해야만 합니다. 그리고 나서 뷰 모델 내의 컬렉션 뷰 클래스에 의해 제공되는 메서드를를 사용해 CurrentChanged 이벤트와 같은 선택 변경 이벤트나 컨트롤 필터링, 정렬, 그룹화에 대한 이벤트들을 구독해야 합니다.


뷰 모델은 ICollectionView 참조를 반환하는 읽기 전용 프라퍼티를 구현해서 뷰의 컨트롤들이 컬렉션 뷰 오브젝트를 데이터 바인딩하고 그것들과 상호작용할 수 있도록 해야 합니다. ItemsControl 을 상속한 모든 WPF 및 실버라이트 컨트롤들은 자동적으로 ICollectionView 클래스들과 상호작용합니다.


다음 코드 예제는 실버라이트에서 PagedCollectionView 를 사용하여 현재 선택된 고객의 트랙을 유지하는 것을 보여 줍니다.



그리고 나서 당신은 뷰에서 ListBox 와 같은 ItemsControl 에 뷰 모델의 Customers 프라퍼티를 바인딩할 수 있는데, 이는 컨트롤의 ItemsSource 프라퍼티를 통해서 이루어 집니다. 그 예는 아래와 같습니다.



사용자가 UI 에서 고객을 선택할 때, 뷰 모델은 그 정보를 전달받을 것이며, 그것은 현재 선택된 고객과 관련된 명령들을 수행할 수 있습니다. 또한 뷰 모델은 컬렉션 뷰의 메서드를 호출함으로써 UI 에서의 현재 선택을 프로그램적으로 변경할 수도 있습니다. 그 예가 아래에 나와 있습니다.



컬렉션 뷰에서 선택이 변경될 때, 아이템의 선택 상태를 가시적으로 표현하기 위해서 UI 가 자동적으로 갱신됩니다. 이 구현은 WPF 에서도 유사한데, 앞의 예제에서의 PagedCollectionView 가 보통 ListCollectionView BindingListCollectionView 로 대체된다는 차이가 있기는 합니다. 그 예가 아래에 나와 있습니다.




커맨드들


뷰에서 출력되고 편집될 데이터에 대한 접근을 제공하는 것과 별개로, 뷰 모델은 사용자에 의해서 수행될 하나 이상의 동작이나 연산을 정의할 것입니다. WPF 와 실버라이트에서, UI 를 통해 사용자가 수행할 수 있는 동작이나 연산은 일반적으로 커맨드로서 정의됩니다. 커맨드들은 UI 의 컨트롤에 쉽게 바인딩될 수 있는 동작이나 연산들을 표현하는 편리한 방식을 제공합니다. 그것들은 그 동작이나 연산들을 구현하는 실제 코드를 캡슐화하고, 그 동작이나 연산들이 뷰에서의 실제 가시적 표현들과 분리될 수 있도록 도와 줍니다.


커맨드들은 그것들이 뷰와 상호작용할 때 다른 방식으로 가시적으로 표현되거나 사용자에 의해 실행될 수 있습니다. 대부분의 경우, 그것들은 마우스 클릭의 결과로서 실행되지만, 그것들은 터치 제스쳐나 다른 입력 이벤트나 단축키 누름의 결과로 실행될 수도 있습니다. 뷰의 컨트롤들은 뷰 모델의 커맨드들에 데이터 바인딩되므로, 사용자는 컨트롤이 정의하는 입력 이벤트나 제스쳐를 사용해서 그것들을 실행할 수 있습니다. 뷰의 UI 컨트롤과 커맨드들 사이의 상호작용은 양방향입니다. 이 경우, 커맨드는 사용자가 UI 와 상호작용할 때 실행될 수 있습니다. 그리고 기저 커맨드가 활성화되거나 비활성화되는 것에 따라, UI 가 자동적으로 활성화되거나 비활성화됩니다.


뷰 모델은 커맨드를 커맨드 메서드나 커맨드 오브젝트로서 구현할 수 있습니다( 커맨드 오브젝트는 ICommand 인터페이스를 구현합니다 ). 두 경우 모두, 뷰의 코드 비하인드 파일 내에서의 복잡한 이벤트 처리 코드를 요구하지 않고도 뷰와 커맨드의 상호작용이 선언적으로 정의될 수 있습니다. 예를 들어, WPF 와 실버라이트의 특정 컨트롤들은 본질적으로 커맨드들을 지원하며, 뷰 모델에 의해서 제공되는 ICommand 오브젝트에 데이터 바인딩될 수 있는 Command 라는 프라퍼티를 제공합니다. 이 외의 경우에, 뷰 모델에 의해 제공되는 커맨드 메서드나 커맨드 오브젝트를 컨트롤에 연관시키기 위해서 커맨드 비헤이비어가 사용될 수 있습니다.


노트:

비헤이비어들은 강력하고 유연한 확장성 메커니즘으로 상호작용 로직이나 동작을 캡슐화합니다. 이는 뷰의 컨트롤들과 선언적으로 연관될 수 있습니다. 커맨드 비헤이비어는 커맨드 오브젝트나 커맨드 메서드를 컨트롤과 연관시키는데 사용될 수 있는데, 해당 컨트롤이 커맨드들과의 상호작용을 위해서 특별히 설계되어 있는 것은 아닙니다.


다음 섹션들에서는 뷰를 위해 커맨드 메서드나 커맨드 오브젝트로서 커맨드를 구현하는 방법을 기술하며, 뷰에서 그것들을 컨트롤들과 연관시키는 방법들에 대해 기술합니다.



커맨드 오브젝트들을 구현하기


커맨드 오브젝트는 ICommand 인터페이스를 구현하는 오브젝트입니다. 이 인터페이스는 Execute 메서드를 정의하는데, 이는 연산 자체를 캡슐화합니다. 그리고 CanExecute 메서드를 제공하는데, 이는 커맨드가 특정 시점에 실행될 수 있는지 여부를 가리킵니다. 이 두 메서드는 모두 파라미터로 커맨드를 받습니다. 커맨드 오브젝트 내에서 연산을 위한 구현 로직을 캡슐화한다는 것은 그것이 좀 더 쉽게 단위 테스트를 위해 사용되거나 유지보수될 수 있다는 것을 의미합니다.


ICommand 인터페이스를 구현하는 것은 직관적입니다. 그러나, 응용프로그램에서 쉽게 사용할 수 있도록 인터페이스를 구현하는 방법은 여러 가지가 있습니다. 예를 들어 Expression Blend SDK 의 ActionCommand 클래스를 사용하거나 프리즘이 제공하는 DelegateCommand 클래스를 사용할 수 있습니다.


프리즘의 DelegateCommand 클래스는 뷰 모델 클래스 내에서 구현된 메서드를 각각 참조하는 두 개의 대리자( delegate ) 를 캡슐화합니다. 이는 DelegateCommandBase 클래스를 상속하고, DelegateCommandBase ICommand 인터페이스의 Execute CanExecute 메서드를 구현하는데, 여기에서는 앞의 대리자들을 호출하도록 하고 있습니다. 당신은 DelegateCommand 클래스 생성자에서 뷰 모델 메서드들에 대한 대리자들을 지정하면 됩니다. 그 예가 아래에 나와 있습니다.



예를 들어, 다음 코드 예제는 Submit 커맨드를 표현하는 DelegateCommand 인스턴스가 OnSubmit 와 CanSubmit 뷰 모델 메서드에 대한 대리자를 지정함으로써 생성되는 방식에 대해서 보여 줍니다. 그리고 나서 그 커맨드는 ICommand 에 대한 참조를 반환하는 읽기 전용 프라퍼티를 통해서 뷰에 노출됩니다.



Execute 메서드가 DelegateCommand 오브젝트에서 호출되면, 그것은 당신이 생성자에서 지정했던 대리자를 통해서 당신의 뷰 모델 클래스의 메서드에 대한 호출을 포워딩할 뿐입니다. 이와 유사하게, CanExecute 메서드가 호출되면, 당신의 뷰 모델 클래스의 관련 메서드가 호출됩니다. 생성자에서 CanExecute 메서드에 대한 대리자는 선택적입니다. 만약 대리자가 지정되지 않으면, DelegateCommand CanExecute 에 대해 항상 true 를 반환할 것입니다.


DelegateCommand 클래스는 저네릭( generic ) 타입입니다. 이 타입 파라미터는 Execute CanExecute 메서드에 넘겨질 커맨드 파라미터의 타입을 지정합니다. 앞의 예제에서, 커맨드 파라미터는 object 타입입니다. 프리즘은 커맨드 파라미터가 요구되지 않을 때 사용할 DelegateCommand 클래스의 비-제너릭 버전도 제공합니다.


뷰 모델은 RaiseCanExecuteChanged 메서드를 DelegateCommand 오브젝트 상에서 호출함으로써 커맨드의 CanExecute 상태의 변경을 지정할 수 있습니다. 이는 CanExecuteChanged 이벤트가 발생하도록 만듭니다. 커맨드에 바인딩된 모든 UI 컨트롤들은 바인딩된 커맨드의 이용 가능 상태를 반영하기 위해서 활성화 상태를 갱신할 것입니다.


ICommand 인터페이스에 대한 다른 구현도 가능합니다. Expression Blend SDK 가 제공하는 ActionCommand 클래스는 프리즘의 DelegateCommand 클래스와 유사합니다. 그러나 그것은 단지 Execute 메서드 대리자만을 지원합니다. 프리즘은 CompositeCommand 클래스도 제공하는데, 이는 DelegateCommand 들이 실행을 위해 서로 그룹화되는 것을 허용합니다. CompositeCommand 클래스를 사용하는 것과 관련된 더 많은 정보를 원한다면, 챕터 6 "진보된 MVVM 시나리오들" 의 "복합 커맨드들" 항목을 찹조하십시오.



뷰에서 커맨드 오브젝트들을 실행하기


뷰의 컨트롤들이 뷰 모델에 의해 제공되는 커맨드 오브젝트들과 연관되는 여러 가지 방식이 존재합니다. 특정 WPF 및 실버라이트 4 컨트롤들은, 특히 Button 이나 RadioButton 과 같은 ButtonBase 를 상속한 클래스들이나 Hyperlink MenuItem 을 상속하는  클래스들은,  Command 프라퍼티를 통해서 커맨드 오브젝트에 쉽게 데이터 바인딩될 수 있습니다. WPF 는 KeyGesture 에 뷰 모델의 ICommand 를 바인딩하는 것도 지원합니다.



커맨드 파라미터는 CommandParameter 프라퍼티를 사용하여 선택적으로 제공될 수도 있습니다. 기대되는 파라미터의 타입은 Execute CanExecute 타깃 메서드들에서 지정됩니다. 컨트롤은 사용자가 컨트롤과 상호작용할 때 자동으로 타깃 커맨드를 실행할 것이며, 만약 커맨드 파라미터가 제공되어 있다면, 그것이 커맨드의 Execute 메서드에 파라미터로 전달될 것입니다. 앞의 예제에서, 버튼은 그것이 클릭될 때 자동으로 SubmitCommand 를 실행할 것입니다. 부가적으로 CanExecute 핸들러가 지정되어 있다면, CanExecute false 를 반환할 때 버튼은 자동으로 비활성화될 것이고, 그것이 true 를 반환할 때 버튼은 자동으로 활성화될 것입니다.


대안 접근법은 Expression Blend 상호작용 트리거들과 InvokeCommandAction 비헤이비어를 사용하는 것입니다.




이 접근법은 상호작용 트리거를 붙일 수 있는 모든 컴트롤을 위해서 사용될 수 있습니다. 그것은 당신이 ButtonBase 를 상속하지 않은 컨트롤에 커맨드를 붙이고자 할 때나, 클릭 이벤트가 아닌 다른 이벤트에서 커맨드를 실행하고자 할 때 특히 유용합니다. 또한 당신이 커맨드를 위한 파라미터를 제공할 필요가 있다면, CommandParameter 프라퍼티를 사용할 수 있습니다.


커맨드에 직접적으로 바인딩될 수 있는 컨트롤들과는 다르게, InvokeCommandAction 은 커맨드의 CanExecute 값들에 기반해 자동적으로 컨트롤을 활성화하거나 비활성화하지는 않습니다. 이 동작을 구현하기 위해서는, 컨트롤의 IsEnabled 프라퍼티를 직접적으로 뷰 모델의 적절한 프라퍼티에 데이터 바인딩해야 합니다. 그 예는 앞에 나와 있습니다.


노트:

커맨드 이용 가능한 컨트롤들 대 비헤이비어들

커맨드를 지원하는 WPF 및 실버라이트 4 컨트롤들은 당신이 선언적으로 컨트롤에 커맨드를 연결할 수 있도록 해 줍니다. 이러한 컨트롤들은 사용자가 컨트롤과 특정 방식으로 상호작용할 때 특정 커맨드를 실행합니다. 예를 들어, Button 컨트롤에 대해, 커맨드는 사용자가 버튼을 클릭할 때 실행됩니다. 커맨드와 연결된 이 이벤트는 고정되어 있으며 변경될 수 없습니다. 또한 비헤이비어들은 선언적인 형태로 커맨드에 컨트롤을 연결할 수 있도록 해 줍니다. 그러나, 비헤이비어들은 컨트롤에 의해 발생하는 이벤트들의 범주에 연관될 수 있으며, 그것들은 뷰 모델의 관련 커맨드 오브젝트나 커맨드 메서드들을 선택적으로 실행하는데 사용될 수 있습니다. 다시 말해, 비헤이비어들은 커맨드 이용 가능한 컨트롤들과 유사한 많은 시나리오들을 가능하게 할 수 있으며, 그것들은 훨씬 많은 수준의 유연성과 제어를 제공할 것입니다.

당신은 커맨드 이용 가능한 컨트롤들을 사용할 때와 비헤이비어를 사용할 때를 선택해야 할 것입니다. 또한 어떤 종류의 비헤이비어를 사용할지도 선택해야 합니다. 만약 당신이 뷰의 컨트롤들을 뷰 모델의 기능과 연관시키기 위한 단일한 메커니즘을 선호하거나 일관성있는 단일 메커니즘을 원한다면, 컨트롤들이 본질적으로 커맨드를 지원하고 있더라도 비헤이비어를 사용하는 것을 고려해야 합니다.

뷰 모델에서 커맨드를 실행하기 위해서 커맨드 이용 가능한 컨트롤을 사용할 필요가 있을 때만, 그리고 커맨드를 실행하기 위해 기본 이벤트들을 사용하는 것이 행복할 때만, 비헤이비어가 요구되지 않을 것입니다.

이와 유사하게, 만약 당신이 Expression Blend 를 사용하지 않는 개발자나 UI 설계자라면, 당신은 커맨드 이용 가능한 컨트롤들( 혹은 custom attached behaviors )을 사용하는 것을 선호하게 될 것입니다. 왜냐하면 Expression Blend 비헤이비어들을 위해서 부가적인 구문들이 요구되기 때문입니다.



뷰에서 커맨드 메서드들을 실행하기


ICommand 오브젝트로서 커맨드들을 구현하는 것에 대한 대안 접근법은, 그것들을 단순히 뷰 모델의 메서드로서 구현하고, 뷰에서 그 메서드들을 직접적으로 호출하기 위해서 비헤이비어들을 사용하는 것입니다.


이는 이전 섹션에서 보여주었던 것처럼 비헤이비어로부터 커맨드들을 실행하는 것과 유사항 방식으로 수행됩니다. 그러나, InvokeCommandAction 을 사용하는 대신에, CallMethodAction 을 사용하게 됩니다. 다음 예제 코드는 기저 뷰 모델의 ( 파라미터 없는 ) Submit 메서드를 호출하는 것을 보여 줍니다.



TargetObject {Binding} 표현식을 통해 ( 뷰 모델을 의미하는 ) 기저 데이터 칸텍스트에 바인딩됩니다. Method 파리미터는 실행될 메서드를 지정합니다.


노트:

CallMethodAction 은 파라미터가 아닙니다; 만약 타깃 메서드에 파라미터를 넘길 필요가 있다면, 뷰 모델의 프라퍼티들을 값으로서 제공해야만 하며, InvokeCommandAction 으로 커맨드를 바꾸거나 당신만의 파라미터를 넘기는 CallMethodAction 버전을 작성해야 합니다.



데이터 유효화와 에러 리포팅


당신의 뷰 모델이나 모델은 종종, 데이터 유효성 검사를 수행하거나 뷰에 데이터 유효성 에러를 표시해서, 사용자가 그것을 수정하는 동작을 수행할 수 있기를 요구받을 것입니다.


실버라이트와 WPF 는 뷰의 컨트롤에 바인딩된 개별 프라퍼티들을 변경할 때 발생하는 데이터 유효성 에러를 관리하기 위한 지원을 제공합니다. 컨트롤에 데이터 바인딩되어 있는 단일 프라퍼티에 대해, 뷰 모델이나 모델은 프라퍼티 setter 에서 데이터 유효성 에러를 표시할 수 있습니다. 이는 예외를 던지거나 들어 온 잘못된 값을 거부함으로써 수행됩니다. 만약 데이터 바인딩할 때 ValidatesOnExceptions 프라퍼티가 true 로 설정되어 있다면, WPF 와 실버라이트의 데이터 바인딩 엔진은 그 예외를 다루고 사용자에게 데이터 유효성 에러가 있음을 가시적으로 보여줄 것입니다.


그러나, 이러한 방식으로 프라퍼티에서 예외를 던지는 것은 가능하면 피해야 합니다. IDataErrorInfo INotifyDataErrorInfo 인터페이스를 뷰 모델이나 모델에서 구현하는 것이 대안적인 접근법입니다. 이 인터페이스들은 뷰 모델이나 모델이 하나 이상의 프라퍼티 값들에 대해서 데이터 유효성 검사를 수행하도록 해 주며, 뷰에 에러 메시지를 반환하도록 해서 사용자가 에러를 통지받을 수 있게 합니다.



IDataErrorInfo 구현하기


IDataErrorInfo 인터페이스는 프라퍼티 데이터 유효화 및 에러 리포팅을 위한 기본적인 지원을 제공합니다. 그것은 두 개의 읽기 전용 프라퍼티들을 정의합니다; 프라퍼티 이름으로 인덱서 인자를 사용하는 인덱서 프파리티와 Error 프라퍼티입니다. 둘 다 문자열 값을 반환합니다.


인덱서 프라퍼티는 뷰 모델이나 모델이 명명된 프라퍼티에 대한 특정한 에러 메시지를 제공하도록 합니다. 빈 문자열이나 null 반환값은 뷰에 변경된 프라퍼티 값이 유효하다는 것을 알려 줍니다. Error 프라퍼티는 뷰 모델이나 모델이 전체 오브젝트에 대한 에러 메시지를 제공하도록 해 줍니다. 그러나 이 프라퍼티는 현재 실버라이트나 WPF 데이터 바인딩 엔진에서 호출되지 않는다는 것을 기억하기 바랍니다.


IDataerrorInfo 인덱서 프라퍼티는 데이터 바인딩된 프라퍼티가 처음 출력될 때, 그리고 그것이 연속적으로 변경될 때마다 접근됩니다. 이 인덱서 프라퍼티는 변경중인 모든 프라퍼티들을 위해 호출되기 때문에, 데이터 유효성 검사가 가능한 한 빠르고 효율적으로 수행되도록 보장하는데 주의를 기울여야만 합니다.


뷰의 컨트롤들에 IDataErrorInfo 인텊이스를 통해서 유효화를 수행하고자 하는 프라퍼티들을 바인딩할 때, 데이터 바인딩의 ValidatesOnDataErrors 프라퍼티를 true 로 설정해야 합니다. 이는 데이터 바인딩 엔진이 데이터 바인딩된 프라퍼티를 위해 에러 정보를 요구하는 것을 보장할 것입니다.




INotifyDataErrorInfo 구현하기


INofityDataErrorInfo IDaraErrorInfo 보다 더 유연합니다. 그것은 프라퍼티를 위한 다중 에러, 비동기 데이터 유효성 검사, 에러 상태가 오브젝트를 위해서 변경될 때 뷰에 통지하는 기능을 지원합니다. 그러나 INotifyDataErrorInfo 는 현재 실버라이트 4 에서만 지원하고 WPF 4 에서는 지원되지 않습니다.


INotifyDataErrorInfo 인터페이스는 HasError 프라퍼티를 정의하는데, 이는 뷰 모델이 에러( 혹은 다중 에러)가 어느 프라퍼티들에서 발생했는지를 알려 주고, 뷰 모델이 개별 프라퍼티들을 위한 에러 메시지 리스트를 반환할 수 있도록 해 주는 GetErrors 메서드를 알려 줍니다.


INotifyDataErrorInfo 인터페이스는 ErrosChanged 이벤트도 정의하고 있습니다. 이는 비동기적인 데이터 유효성 검사 시나리오를 지원하는데, ErrorsChanged 이벤트를 통해 뷰나 뷰 모델이 개별 프라퍼티들에 대한 에러 상태 변경을 표시할 수 있도록 해 줍니다. 프라퍼티 값들은 데이터 바인딩 이외에도 다양한 방식으로 변경될 수 있습니다 - 예를 들어, 웹 서비스 호출이나 백그라운드 계산의 결과로서 변경될 수 있습니다. ErrorsChanged 이벤트는 데이터 유효성 에러가 식별될 때 뷰 모델이 에러가 난 뷰에 정보를 줄 수 있도록 해 줍니다.


INotifyDataErrorInfo 를 지원하기 위해서는, 각 프라퍼티를 위한 에러 리스트를 유지할 필요가 있습니다. Model-View-ViewModel Reference Implementation( MVVM RI ) 는 오브젝트의 모든 유효성 에러를 트래킹하는 ErrosConatiner 컬렉션 클래스를 사용해 이를 수행하는 방식에 대해 설명합니다. 그것은 또한 에러 리스트가 변경되었을 때 통지 이벤트들을 발생시킵니다. 다음 예제 코드는 DomainObject( 루트 모델 오브젝트 )를 보여 주고, ErrorsContainer 클래스를 사용하는 INotifyDataErrorInfo 에 대한 샘플 구현을 보여 줍니다.



실버라이트에서, 뷰 모델의 프라퍼티들에 바인딩된 모든 컨트롤들은 자동적으로 INotifyDataErrorInfo 이벤트를 구독하며, 프라퍼티가 에러를 포함할 때 컨트롤에 에러 정보를 출력합니다.



생성 및 엮기( wire-up )



MVVM 패턴은 UI 를 프리젠테이션 로직, 비즈니스 로직, 데이터와 명확하게 분리할 수 있도록 도와줍니다. 그래서 올바른 클래스로 올바른 구현을 하는 것이 MVVM 패턴을 효율적으로 사용하기 위해 중요한 첫 번째 단계입니다. 뷰와 뷰 모델간이 데이터 바인딩과 커맨드를 통해서 상호작용하도록 관리하는 것도 고려해야 할 중요한 관점입니다. 다음 단계는 뷰, 뷰 모델, 모델 클래스을 인스턴스화하고 런타임에 서로 연관시키는 방법을 고려하는 것입니다.


노트:

이 단계를 관리하기 위한 적절한 전략을 선택하는 것은 응용프로그램에서 종속성 주입 컨테이너를 사용하고자 한다면 특히 중요합니다. MEF 와 유니티는 모두 뷰, 뷰 모델, 모델 사이의 종속성들을 지정하는 기능을 제공하고 있으며, 컨테이터에 의해서 그것들을 수행합니다. 진보된 시나리오에 대한 더 많은 정보를 원한다면, 챕터 6의 "진보된 MVVM 시나리오들" 을 참조하십시오.


일반적으로, 뷰와 그것의 뷰 모델 간에는 1 대 1 관계가 성립합니다. 뷰와 뷰 모델은 뷰의 데이터 칸텍스트 프라퍼티를 통해 느슨하게 연결됩니다; 이는 뷰에서의 비주얼 엘리먼트들과 비헤이비어들이 뷰 모델의 프라퍼티, 커맨드, 메서드들에 데이터 바인딩되도록 해 줍니다. 당신은 뷰와 뷰 모델 클래스의 인스턴스화를 관리하는 방식과 그것들을 런타임에 DataContext 프라퍼티를 통해 연관시키는 방식을 결정할 필요가 있습니다.


뷰 와 뷰 모델을 생성하고 연결할 때 느슨한 결합이 유지되도록 보장하기 위해서 주의를 기울여야만 합니다. 이전 섹션에서 주의를 줬듯이, 뷰 모델은 이상적으로는 뷰에 대한 특정 구현에 의존성을 가져서는 안 됩니다. 이와 유사하게, 뷰는 이상적으로는 뷰 모델의 특정 구현에 의존성을 가져서는 안 됩니다.


노트:

그러나, 뷰는 묵시적으로 뷰 모델의 특정 프라퍼티, 커맨드, 메서드에 의존할 것이라는 점을 기억해야 합니다. 왜냐하면 그것이 데이터 바인딩을 정의하고 있기 때문입니다. 만약 뷰 모델이 요구되는 프라퍼티, 커맨드, 메서드를 구현하지 않았다면, 런타임 예외가 데이터 바인딩 엔진에 의해서 생성될 것이며, 이는 디버깅 동안에 비주얼 스튜디오의 아웃풋 윈도우에 출력될 것입니다.


뷰와 뷰 모델이 런타임에 생성되고 연관되는 방식은 다양합니다. 가장 적절한 접근법은, 당신이 뷰나 뷰 모델을 생성하느냐, 그리고 이를 프로그램적으로 하느냐 선언적으로 하느냐에 달려있을 것입니다. 다음 섹션들은 뷰와 뷰 모델이 런타임에 생성되고 연관되는 일반적인 방식들에 대해 설명합니다.



XAML 을 사용해서 뷰 모델을 생성하기


아마도 뷰를 위한 가장 간단한 접근법은 XAML 에서 뷰와 연관된 뷰 모델을 XAML 에서 선언적으로 인스턴스화하는 것입니다. 뷰가 생성될 때, 관련 뷰 모델 오브젝트도 생성될 것입니다. 당신은 XAML 에서 뷰 모델이 뷰의 데이터 칸텍스트에 설정되었음을 지정할 수도 있습니다.


XAML 기반 접근법은 Basic MVVM QuickStart 의 QuestionnaireView.xaml 파일에 설명되어 있습니다. 그 예제에서, QuestionnaireViewModel 인스턴스는 QuestionnaireView 의 XXAML 에서 정의됩니다. 그 예는 다음과 같습니다.



QuestionnaireView 가 생성될 때, QuestionnaireViewModel 이 자동적으로 생성되며, 뷰의 데이터 칸텍스트로 설정됩니다. 이 접근법은 당신의 뷰 모델이 기본 ( 파라미터 없는 ) 생성자를 가질 것을 요구합니다.


뷰에 의한 뷰 모델의 선언적인 생성 및 할당의 이점은 그것이 단순하고 Microsoft Expression Blend 나 Microsoft Visual Studio 와 같은 디자인 타임 툴들에서 잘 동작한다는 거입니다. 이 접근법의 단점은 뷰가 관련 뷰 모델 타입에 대한 정보를 가지고 있다는 것입니다.



프로그램적으로 뷰 모델 생성하기


이 접근법은 뷰와 연관된 뷰 모델을 프로그램적으로 뷰의 생성자에서 인스턴스화하는 것입니다. 그리고 나서 뷰의 데이터 칸텍스트에 뷰 모델을 설정할 수 있습니다. 그 예가 아래에 나와 있습니다.



뷰 모델을 뷰의 코드-비하인드에서 프로그램적으로 생성하고 할당하는 것의 이점은 그것이 단순하며 Expression Blend 나 Visual Studio 와 같은 디자인 타임 툴에서도 잘 동작한다는 것입니다. 이 접근법의 단점은 뷰가 연관된 뷰 모델 타입에 대한 정보를 가질 것을 필요로 한다는 것이며, 뷰의 코드-비하인드 내의 코드를 요구한다는 것입니다. 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용하는 것은 뷰와 뷰 모델 간의 느슨한 결합을 유지하는데 도움을 줍니다. 더 많은 정보를 원한다면, 챕터 3 "컴포넌트 간의 종속성 관리하기" 를 참조하십시오.



데이터 템플릿으로 정의된 뷰 생성하기


뷰는 데이터 템플릿으로 정의될 수 있으며, 뷰 모델 타입과 연관될 수 있습니다. 데이터 템플릿은 리소스로서 정의되거나, 뷰 모델을 출력하게 될 컨트롤 내부에 포함되어 정의될 수 있습니다. 컨트롤의 "내용" 은 뷰 모델 인스턴스이며, 데이터 템플릿은 그것을 가시적으로 표현하는데 사용됩니다. WPF 와 실버라이트는 데이터 템플릿을 런타임에 자동적으로 인스턴스화하며 그것의 데이터 칸텍스트를 뷰 모델의 인스턴스로 설정합니다. 이 기법은 뷰 모델이 먼저 생성되고 뷰가 나중에 생성되는 상황의 예입니다.


데이터 템플릿은 유연하고 가볍습니다. UI 설계자는 그것들을 사용해 복잡한 코드를 요구하지 않고도 뷰 모델에 대한 가시적 표현을 쉽게 정의할 수 있습니다. 데이터 템플릿들은 뷰에 제한되는데, 그것은 어떤 UI 로직( 코드-비하인드 )도 요구하지 않습니다. Microsoft Expression Blend 는 데이터 템플릿을 가시적으로 설계하고 편집하기 위해서 사용될 수 있습니다.


다음 예제는 고객 리스트에 바인딩되는 ItemsControl 을 보여 줍니다. 기저 컬렉션의 각 고객 오브젝트는 뷰 모델 인스턴스입니다. 고객을 위한 뷰는 인라인 데이터 템플릿으로 정듸됩니다. 다음 예제에서, 각 고객 뷰 모델을 위한 뷰는 StackPanel  로 정의되는데, 그것은 레이블과 뷰 모델의 Name 프라퍼티에 바인딩된 텍스트 박스로 구성되어 있습니다.



당신은 데이터 템플릿을 리소스로서 정의할 수도 있습니다. 다음 예제는 리소스로 정의된 데이터 템플릿을 보여 주고, StaticRresource 마크업 확장을 통해서 클라이언트 컨트롤에 적용되는 것을 보여 줍니다.



여기에서, 데이터 템플릿은 concrete 뷰 타입을 래핑합니다. 이는 뷰가 코드-비하인드 비헤이비어를 정의하도록 해 줍니다. 이러한 방식으로, 데이터 템플릿 메커니즘은 외부적으로 뷰와 뷰 모델의 연관을 제공하기 위해서 사용될 수 있습니다. 비록 앞의 예제가 UserControl 리소스 안의 템플릿을 보여 주기는 하지만, 그것은 재사용을 위해서 보통 응용프로그램의 리소스에 배치될 것입니다. 데이터 템플릿을 사용하여 뷰를 인스턴스화하고 그것들을 자신의 뷰 모델에 연관시키는 예제를 MVVM QuickStart 의 QuestionnaireView.xaml 에서 찾아볼 수 있습니다.



핵심 결정들



당신의 응용프로그램을 생성하는데 있어 MVVM 패턴을 사용하기로 했다면, 나중에 변경하기 힘든 특정 설계 결정들을 내려야 할 필요가 있습니다. 보통, 이 결정들은 응용프로그램 전반적인 이슈이며, 그것들을 응용프로그램 전반에서 일관성있게 사용하는 것은 개발자나 디자이너의 제품성을 증대시켜줄 것입니다. 다음은 MVVM 패턴을 구현할 때 가장 중요한 결정들을 요약합니다:

  • 당신이 사용할 뷰 및 뷰 모델의 생성과 관련한 접근법을 결정하십시오. 응용프로그램이 뷰나 뷰 모델을 먼저 생성할지, 유니티나 MEF 와 같은 종속성 주입 컨테이너를 사용할지를 결정할 필요가 있습니다. 당신은 보통 이것이 응용프로그램 전반에서 일관적이기를 원할 것입니다. 더 많은 정보를 원한다면, 이 챕터의 "생성 및 엮기" 섹션을 참조하십시오. 그리고 챕터 6 "진보된 MVVM 시나리오들" 의 "진보된 생성 및 엮기" 를 참조하십시오.
  • 뷰 모델로부터의 커맨드를 커맨드 메서드로 노출할지 커맨드 오브젝트로 노출할지를 결정하십시오. 커맨드 메서드는 노출하기 쉬우며 뷰의 비헤이비어를 통해서 실행될 수 있습니다. 커맨드 오브젝트는 커맨의와 활성화/비활성화 로직을 깔끔하게 캡슐화할 수 있으며, 비헤이비어나 ButtonBase-상속 클래스의 Command 프라퍼티를 통해서 실행될 수 있습니다. 개발자나 디자이너가 사용하기 쉽게 하기 위해서, 이를 응용프로그램 전반에서 사용하기로 하는 선택하는 것은 좋은 생각입니다. 더 많은 정보를 원한다면, 이 챕터의 "커맨드들" 섹션을 참조하십시오.
  • 뷰 모델과 모델이 뷰에 에러를 제출하는 방식을 결정하십시오. 당신의 모델은 IDataErrorInfo 나, 실버라이트를 사용할 경우, INotifyDataErrorInfo 를 지원할 수 이습니다. 모든 모델이 에러 정보를 제출할 필요는 없지만, 그래야 하는 경우에, 당신의 개발자들을 위해 일관적인 접근을 하는 것이 좋습니다. 더 많은 정보를 원한다면, 이 챕터의 "데이터 유효화 및 에러 리포팅' 을 참조하십시오.
  • Microsoft Expression Blend 디자인-타임 데이터 지원이 당신의 팀에 있어 중요한지를 결정하십시오. 만약 Expression Blend 를 UI 를 설계하고 유지보수하는데 사용하고 디자인 타임 데이터를 보고 싶다면, 당신의 뷰와 뷰 모델이 파라미터를 가지지 않는 생성자를 제공하고 뷰가 디자인-타임 데이터 칸텍스트를 제공하도록 보장하십시오. 대안적으로, d:DataContextd:DesignSource 와 같은 디자인-타임 애트리뷰트들을 사용하여 Microsoft Expression Blend 가 제공하는 디자인-타임 기능들을 사용하는 것을 고려하십시오. 더 많은 정보를 원한다면, 챕터 7 "유저 인터페이스 만들기" 의 "디자이너 친화적인 뷰를 생성하는 가이드라인" 을 참조하십시오.


더 많은 정보



For more information about data binding in WPF, see "Data Binding" on MSDN:

For data binding in Silverlight, see "Data Binding" on MSDN:

For more information about binding to collections in WPF, see "Binding to Collections" in "Data Binding Overview" on MSDN:

For more information about binding to collections in Silverlight, see "Binding to Collections" in "Data Binding" on MSDN:

For more information about the Presentation Model pattern, see "Presentation Model" on Martin Fowler's website:

For more information about data templates, see "Data Templating Overview" on MSDN:

For more information about MEF, see "Managed Extensibility Framework Overview" on MSDN:

For more information about Unity, see "Unity Application Block" on MSDN:

For more information about DelegateCommand and CompositeCommand, see Chapter 9, "Communicating Between Loosely Coupled Components."


'Programming > Prism4.1' 카테고리의 다른 글

진보된 MVVM 시나리오들  (0) 2014.09.20
모듈러 응용프로그램 개발  (0) 2014.09.13
컴포넌트 간의 종속성 관리하기  (0) 2014.09.10
프리즘 응용프로그램 초기화하기  (0) 2014.08.24
왜 프리즘을 사용하는가?  (0) 2014.08.19
소개  (0) 2014.08.18
프리즘 4.1 설치  (0) 2014.08.14

프리즘 4.1 도움말 번역입니다



모듈러 응용프로그램 개발



모듈러 응용프로그램은 더 큰 응용프로그램으로 통합될 수 있는 ( 모듈이라는 이름을 가진 ) 기능적 유닛들의 집합으로 구성된 응용프로그램을 말합니다. 클라이언트 모듈은 응용프로그램의 전체적인 기능들의 일부를 캡슐화함며, 일반적으로 연관된 관심사들의 집합을 표현합니다. 그것은 응용프로그램 기능과 같은 관련 컴포넌트들의 집합을 포함하는데, 이 컴포넌트 집합은 사용자 인터페이스와 비즈니스 로직, 또는 로깅이나 구사용자 인증과 같은 응용프로그램-레벨 서비스들과 같은 응용프로그램 인프라스트럭쳐의 일부를 포함합니다. 모듈들은 서로 돌립적이지만, 느슨학 결합되는 방식으로 서로 통신할 수도 있습니다. 모듈러 응용프로그램들은 응용프로그램을 개발하고 테스트하고 배포하고 확장하기 쉽게 만들어 줍니다.


예를 들어, 개인 뱅킹 응용프로그램을 고려해 보십시오. 사용자는 단일한 유저 인터페이스를 통해 계정간에 돈을 이체하거나, 세금을 내거나, 개인 정보를 갱신하는 것과 같은 다양한 기능들에 접근할 수 있습니다. 그러나 보이는 장면 뒤에는, 이러한 각 기능들이 분리된 모듈로 캡슐화되어 있습니다. 이 모듈들은 서로 통신하며, 데이터베이스 서버나 웹 서비스들과 같은 백엔드 시스템들과 통신하고 있습니다. 응용프로그램 서비스들은 서로 다른 모듈들 내에 존재하는 다양한 컴포넌트들을 통합하고, 사용자와의 통신을 제어합니다. 사용자는 단일 응용프로그램처러 보이는 통합된 뷰를 보게 됩니다.


다음 그림은 다중 모듈들을 가진 모듈러 응용프로그램의 설계를 보여 줍니다.


모듈 조합




모듈러 응용프로그램들을 빌드하는 것의 이점들



당신은 아마도 어셈블리, 인터페이스, 클래스, 좋은 개체지향 설계 원칙의 도입을 통해 잘 구조화된 응용프로그램을 이미 빌드하고 있을 것입니다. 그렇지만, 별로 신경쓰지 않고 있다면, 당신의 응용프로그램은 아마 여전히 모놀리식( 모든 기능들이 응용프로그램 내부에서 강하게 결합되는 방식으로 구현되는 것을 의미 )일 것이며, 이는 응용프로그램을 개발하고, 테스트하고, 확장하고, 유지보수하기 힘들게 만들 수 있습니다.


반면에, 모듈러 응용프로그램 접근법은 응용프로그램의 대규모 기능 영역들을 구분하는데 도움을 줄 수 있으며, 그 기능들을 독립적으로 개발하고 테스트할 수 있도록 해 줍니다. 이는 개발 및 테스팅을 쉽게 만들어 줄 뿐만 아니라, 응용프로그램을 더욱 유연하게 만들어 주며, 나중에 확장하기 쉽게 만들어 줍니다. 모듈러 접근법의 이점은 당신의 전체 응용프로그램 구조가 더욱 유연하고 유지보수 가능하도록 만들어 줄 수 있다는 것입니다. 왜냐하면 그것은 응용프로그램을 관리 가능한 조각들로 나눌 수 있게 해 주기 때문입니다. 각 조각들은 특정 기능들을 캡슐화하고, 각 조각들은 명료하지만 느슨하게 결합된 대화 채널들을 통해서 통합됩니다.


모듈러 응용프로그램 개발을 위한 프리즘의 지원


프리즘은 모듈러 응용프로그램 개발 및 실시간 모듈 관리를 위한 지원을 제공합니다. 프리즘의 모듈러 개발 기능을 사용하면 시간을 절약할 수 있습니다. 왜냐하면 당신이 자신의 모듈성 프레임워크를 구현하고 테스트할 필요가 없어지기 때문입니다. 프리즘은 다음의 모듈러 응용프로그램 개발 기능을 지원합니다:

  • 이름있는 모듈들과 그것의 위치를 등록하기 위한 모듈 카탈로그를 제공; 당신은 다음과 같은 방식으로 모듈 카탈로그를 생성할 수 있습니다:
    • 코드나 Extensible Application Markup Language( XAML )에서 모듈을 정의함으로써...
    • Windows Presentation Foundation( WPF ) 에 대해 : 디렉토리 내의 모듈들을 검색함으로써, 중앙관리되는 카탈로그 내에 명시적으로 모듈들을 정의하지 않아도, 당신의 모든 모듈들을 로드할 수 있습니다.
    • WPF 에 대해: 구성 파일에 모듈들을 정의함으로써...
  • 초기화 모드와 종속성들을 지원하기 위해서, 모듈에 대한 선언적인 메타데이터 애트리뷰트들을 제공.
  • 모듈들 사이의 느슨한 결합을 지원하기 위해서, 종속성 주입 컨테이너와의 통합을 제공.
  • 모듈 로딩을 위해서:
    • 모듈들이 올바른 순서로 한 번만 로드되고 초기화되는 것을 보장하기 위해서, 중복 검사와 사이클 검사를 포함하는 종속성 관리를 제공합니다.
    • 응용프로그램 시작 시간을 최소화하기 위해서 모듈들에 대한 온디맨드 로딩 및 백그라운드 로딩을 제공합니다; 남은 모듈들은 백그라운드로 혹은 요구되는 시점에 로드되고 초기화될 수 있습니다.

핵심 개념들


이 섹션은 프리즘의 모듈성과 관련된 핵심 개념들을 소개하는데, IModule 인터페이스, 모듈 로딩 과정, 모듈 카탈로그, 모듈간 통신, 종속성 주입 컨테이너들과 관련한 내용들을 포함합니다.

IModule : 모듈러 응용프로그램들의 빌딩 블락


모듈은 기능에 대한 논리 컬렉션이며 리소스인데, 이는 개별적으로 개발되고, 테스트되고, 배포되고, 응용프로그램에 통합될 수 있는 방식으로 패키징됩니다. 패키지는 하나 이상의 어셈블리일 수 있으며, 느슨한 컬렉션이나 XAP 파일 내에 묶여 있을 수 있습니다. 각 모듈은 그 모듈을 초기화하고 응용프로그램에 그것의 기능을 통합하는 책임이 있는 중앙 클래스를 가지게 됩니다. 그 클래스는 IModule 인터페이스를 구현합니다. IModule 인터페이스를 구현하는 클래스의 존재는 그 패키지를 모듈로서 식별하는데 충분합니다. IModule 인터페이스는 Initialize 라고 명명된 단일 메서드를 가지는데, 이 안에서 당신은 모듈을 초기화하고 응용프로그램에 모듈의 기능성을 통합하기 위해서 요구되는 로직을 구현할 수 있습니다. 모듈의 목적에 따라, 뷰를 복합 유저 인터페이스에 추가하거나, 응용프로그램에서 이용 가능한 부가적인 서비스들을 만들거나, 응용프로그램의 기능성을 확장할 수 있습니다. 다음 코드는 모듈을 위한 최소 구현을 보여 줍니다.



노트:

IModule 인터페이스에 의해 제공되는 초기화 메커니즘을 사용하는 대신에, Stock Trader RI 는 뷰, 서비스, 타입을 등록하기 위해 선언적인 애트리뷰트 기반 접근법을 사용합니다.


모듈 라이프타임


프리즘에서 모듈 로딩 프로세스는 다음을 포함합니다:

  1. 모듈을 등록하고 검색하기. 특정 응용프로그램을 위해 런타임에 로드될 모듈들은 모듈 카탈로그에 정의됩니다. 이 카탈로그는 로드될 모듈에 대한 정보, 그것들의 위치, 그리고 로드될 순서에 대한 정보를 포함합니다.
  2. 모듈 로드하기. 모듈들을 포함하는 어셈블리는 메모리에 로드됩니다. 이 단계는 모듈들이 웹으로부터 다운로드되거나, 원격 위치나 로컬 디렉토리로부터 획득될 것을 요청합니다.
  3. 모듈 초기화하기. 그 다음, 모듈들이 초기화됩니다.  이는 모듈 클래스의 인스턴스를 생성하고 IModule 인터페이스를 통해 그것들의 Initialize 메서드를 호출하는 것을 의미합니다.

다음 그림은 모듈 로딩 과정을 보여줍니다.

모듈 로딩 과정





모듈 카탈로그


ModuleCatalog 는 응용프로그램에서 사용할 수 있는 모듈들에 대한 정보를 저장합니다. 이 카탈로그는 본질적으로 ModuleInfo 클래스들의 컬렉션입니다. 각 모듈은 ModuleInfo 클래스에서 기술되는데, 이는 이름, 타입, 위치, 그리고 모듈의 다른 애트리뷰트들을 기록합니다. ModuleCatalog ModuleInfo 인스턴스들로 채우기 위한 몇 가지 일반적인 접근법들이 존재합니다:

  • 코드에서 모듈들을 등록하기
  • XAML 에서 모듈들을 등록하기
  • 구성 파일에서 모듈들을 등록하기( WPF 만 )
  • 디스크의 로컬 디렉토리에서 모듈들을 검색하기( WPF 만 )


당신이 사용할 모듈 등록 및 검색 메커니즘은 응용프로그램의 요구에 달려 있습니다. 구성 파일이나 XAML 파일을 사용하는 것은 응용프로그램이 모듈에 대한 레퍼런스들을 요구하지 않도록 해 줍니다. 디렉토리를 사용하는 것은 응용프로그램이 파일에 그것들을 지정하지 않고도 모듈들을 검색할 수 있게 해 줍니다.


모듈을 로드할 시점을 제어하기


프리즘 응용프로그램들은 가능한 한 빨리 모듈들을 초기화할 수 있는데, 이는 "이용 가능할 때( when available )" 로 알려져 있습니다. 그리고 응용프로그램이 그것을 필요로 할 때 초기화할 수 있는데, 이는 "온디맨드( 요청시, on-demand )"로 알려져 있습니다. 실버라이트 응용프로그램에 대해, 모듈들은 응용프로그램과 함께 다운로드되거나, 응용프로그램 시작 후에 백그라운드로 다운로드될 수 있습니다. 모듈을 로드하기 위한 다음 가이드라인을 고려하십시오:

  • 응용프로그램을 실행하기 위해서 요구되는 모듈들은 반드시 응용프로그램과 함께 다운로드되어야만 하고, 응용프로그램이 실행될 때 초기화되어야만 합니다.
  • 응용프로그램의 일반적인 용도를 위해서 항상 사용되는 기능들을 포함하는 모듈들은 그것들이 이용 가능해질 때 백그라운드로 다운로드되거나 초기화될 수 있습니다.
  • 거의 사용되지 않는 모듈들은( 혹은 다른 모듈들을 위해서 선택적인 의존성을 가지는 지원 모듈들은 ) 백그라운드로 다운로드되고 요청시에 초기화될 수 있습니다.

응용프로그램에 모듈들을 통합하기

프리즘은 응용프로그램을 구동( bootstrap )하기 위해 다음의 클래스들을 제공합니다: UnityBootstrapper MefBootstrapper. 이 클래스들은 모듈들을 검색하거나 로드하기 위한 모듈 관리자를 생성하고 구성하는데 사용될 수 있습니다. XAML 파일이나, 구성 파일, 몇 줄의 코드에 기입된 디렉토리 위치에서 지정된 모듈들을 등록하기 위해 구성 메서드를 재정의할 수 있습니다.

모듈 Initialize 메서드를 사용해서 모듈들을 응용프로그램의 나머지 부분과 통합하십시오. 당신이 택할 수 있는 방식은 다양하며, 이는 응용프로그램 구조와 모듈의 내용에 의존하게 됩니다. 다음은 모듈을 응용프로그램ㄹ에 등록하기 위해서 수행해야 하는 공통적인 작업들입니다:
  • 모듈의 뷰를 응용프로그램의 네비게이션 구조에 추가하십시오. 이는 복합 UI 응용프로그램을 뷰 검색이나 뷰 주입을 사용해 빌드할 때 공통적인 작업입니다.
  • 응용프로그램 레벨 이벤트나 서비스를 구독( subscribe )하십시오.
  • 공유 서비스들을 응용프로그램의 종속성 주입 컨테이너에 등록하십시오.

모듈 간에 통신하기


비록 모듈들이 모듈간에 저수준 결합을 하고 있기는 하지만, 모듈간에 서로 통신하는 것은 일반적인 상황입니다. 몇 가지 느슨하게 결합된 통신 패턴이 존재하는데, 각각은 자신만의 강도를 가집니다. 일반적으로, 이 패턴들의 조합은 결과적인 솔루션을 생성하는데 사용됩니다. 다음은 이러한 패턴의 일부를 소개합니다:

  • 느슨하게 결합된 이벤트들. 모듈은 발생한 특정 이벤트를 발송할( broadcast )수 있습니다. 다른 모듈들은 그 이벤트를 구독해서, 언제 이벤트가 발생했는지를 통지받을 것입니다. 느슨하게 결합된 이벤트들은 두 모듈 사이에 통신을 설정하기 위한 가벼운 방식입니다; 그러므로, 그것들은 쉽게 구현됩니다. 그러나, 너무 이벤트에 의존하는 설계는 단일 작업을 만족시키기 위해서 서로를 열심히 조율하는 일을 필요로 하게 만듭니다. 이 경우, 공용 서비스를 고려하는 것이 더 나을 것입니다.
  • 공유 서비스. 공유 서비스는 공통 인터페이스를 통해 접근될 수 있는 클래스를 의미합니다. 일반적으로, 공유 서비스는 공유 어셈블리에서 발견되며, 인증이나 로깅, 혹은 구성과 같이 전 시스템에 걸친 서비스를 제공합니다.
  • 공유 리소스. 만약 당신이 직접적으로 다른 모듈과 서로 통신하는 모듈들을 원하고 있다면, 간접적으로 데이터베이스나 웹 서비스 집합과 같은 공유 리소스들을 통해 그것들을 통신하게 만들 수 있습니다.

의존성 주입과 모듈러 응용프로그램


유니티 응용프로그램 블락( Unity ) 와 관리되는 의존성 프레임워크( MEF ) 같은 컨테이너들은 쉽게 제어 역전( Inversion of Control,  IoC ) 과 의존성 주입을 사용할 수 있게 해 줍니다. 이는 느슨하게 결합하는 방식으로 컴포넌트를 조립하는데 도움을 주는 강력한 설계 패턴입니다. 제어 역전은 어떤 컴포넌트가 자신이 의존하고 있는 다른 컴포넌트에 대한 레퍼런스를 획득하기 위해, 그 레퍼런스에 대한 하드코딩을 하지 않고도, 레퍼런스를 획득할 수 있도록 해 줍니다. 결과적으로 코드 재사용성을 높이고 유연성을 증가시킬 수 있도록 합니다. 의존성 주입은 느슨하게 결합된 모듈러 응용프로그램을 빌드할 때 유용합니다. 프리즘은 응용프로그램 내에서 컴포넌트를 조립하기 위해서 사용되는 의존성 주입 컨테이너에 대한 불가지론( agnostic )으로 설계되었습니다. 컨테이너에 대한 선택은 당신에게 달려 있으며, 그것은 응용프로그램 요구사항 및 선호도에 크게 의존합니다. 그러나, 마이크로소프트가 제안하는 두 가지 주요 의존성 주입 프레임워크가 존재하기는 합니다 - Unity 와 MEF 가 바로 그것입니다.


패턴 & 경험의 유니티 응용프로그램 블락은 완전한 기능을 갖춘 의존성 주입 컨테이너를 제공합니다. 그것은 프라퍼티-기반 주입, 생성자-기반 주입, 그리고 정책( policy ) 주입을 지원합니다. 이는 당신이 컴포넌트 간에 행위와 정책을 투명하게 주입할 수 있도록 해 줍니다; 또한 의존성 주입 컨테이너에서 일반적인 다른 기능들에 대한 호스트도 지원합니다.


( 이제 닷넷 프레임워크 4 와 실버라이트 4 의 일부인 ) MEF는 의존성 주입-기반 컴포넌트 조립을 지원함으로써 확장성 있는 닷넷 응용프로그램을 빌드하기 위한 지원을 제공합니다. 그리고 모듈러 응용프로그램 개발을 지원하는 다른 기능들을 제공합니다. 이는 응용프로그램이 런타임에 컴포넌트들을 검색하고 그 컴포넌트들을 응용프로그램에 느슨하게 결합된 방식으로 통합할 수 있도록 해 줍니다. MEF 는 훌륭한 확장성 및 복합 프레임워크입니다. 이는 어셈블리, 타입 검색, 타임 종속성 결정( resolution ), 의존성 주입, 그리고 일부 좋은 어셈블리와 XAP 다운로드 기능들을 제공합니다. 프리즘은 MEF 기능의 이점을 취하기 위한 지원 외에도 다음을 지원합니다:

  • 모듈 타입과 그것의 XAP 위치를 연관시킵니다.
  • WPF 와 실버라이트 모두에 대해, XAML 과 코드 애트리뷰트를 통한 모듈 등록을 지원합니다.
  • WPF 에 대해, 구성 파일과 디렉토리 스캔을 통한 모듈 등록을 지원합니다.
  • 모듈이 로드될 때 상태 트래킹을 지원합니다.
  • MEF 가 사용될 때 모듈에 대한 선언적인 커스텀 메타데이터를 지원합니다.
유니티와 MEF 는 프리즘을 사용해 유사하게 동작합니다.

핵심 결정들


첫 번째 해야 할 결정은 어떤 모듈러 솔루션을 개발할 것인가 입니다. 이전 섹션들에서 논의된 것처럼, 모듈러 응용프로그램들을 빌드하는 것은 다양한 이점들을 가지고 있습니다만, 이러한 이점들을 취하기 위해 당신이 들여야 하는 시간과 노력의 관점에서 생각을 해 볼 필요가 있습니다. 만약 모듈러 응용프로그램을 개발하기로 했다면, 고려해야 할 점이 몇 가지 있습니다:
  • 당신이 사용할 프레임워크를 결정하십시오. 당신은 자신만의 모듈성 프레임워크를 생성할 수도 있고, 프리즘, MEF 나 다른 프레임워크를 사용할 수도 있습니다.
  • 당신의 솔루션을 조직할 방식을 결정하십시오. 어떤 어셈블리가 각 모듈의 일부가 될 것인지를 포함해, 각 모듈의 경계를 정의함으로써 모듈러 구조에 접근하십시오. 당신은 개발을 쉽게 하기 위해서 모듈성을 사용하기로 결심할 수도 있으며, 또한 그것이 플러그인을 지원하거나 확장성있는 구조를 지원하게될 때 응용프로그램이 배포되는 방식을 제어해야만 하겠다고 결심할 수도 있습니다.
  • 당신의 모듈들을 나누는 방식을 결정하십시오. 모듈들은 요구에 따라 서로 다르게 나뉠 수 있습니다. 예를 들어, 기능 영역, 프로바이더 모듈들, 개발 팀, 배포 요구사항에 의해 나뉠 수 있습니다.
  • 응용프로그램이 모든 모듈들에 대해 제공해야할 핵심 서비스들을 결정하십시오. 핵심 서비스는 에러 기록 서비스이거나 인증 서비스이거나 권한 서비스일 수 있습니다.
  • 만약 프리즘을 사용하고 있다면, 모듈을 모듈 카탈로그에 등록하기 위해서 사용할 접근법을 결정하십시오. WPF 에 대해, 당신은 코드, XAML, 구성 파일, 디스크의 로컬 디렉토리에서 검색된 모듈에서 모듈을 등록할 수 있습니다. 실버라이트에 대해, 당신은 코드나 XAML 에서 모듈들을 등록할 수 있습니다.
  • 당신의 모듈 통신 전략 및 의존성 전략을 결정하십시오. 모듈들은 서로 통신할 필요가 있을 것입니다. 그리고 당신은 모듈간의 종속성들을 다룰 필요가 있습니다.
  • 의존성 주입 컨테이너를 결정하십시오. 일반적으로, 모듈러 시스템은 의존성 주입, 제어 역전, 서비스 로케이터를 요구하며, 이것들을 통해 느슨한 결합과 모듈에 대한 동적 로딩 및 생성이 허용됩니다. 프리즘은 유니티, MEF, 혹은 다른 컨테이너를 사용하는 것을 허용하며, 유니티나 MEF 기반 응용프로그램들을 위한 라이브러리들을 제공합니다.
  • 응용프로그램 시작 시간을 최소화하십시오. 응용프로그램 시작 시간을 최소화하기 위해서, 모듈에 대한 온디맨드 로딩 및 백그라운드 다운로딩을 고려하십시오.
  • 배포 요구사항을 결정하십시오. 당신의 응용프로그램이 배포되는 방식에 대해서 생각할 필요가 있습니다. 이는 XAP 에 설정할 어셈블리의 개수에 영향을 줍니다. 또한 실버라이트에서 어셈블리 캐싱의 이점을 취하기 위해서는 프리즘 라이브러리와 같은 공유 라이브러리를 분할해야 할 것입니다.
다음 섹션에서는 이러한 결정들에 대한 세부 사항들을 제공합니다.

응용프로그램을 모듈로 분할하기

응용프로그램을 모듈화된 방식으로 개발할 때, 응용프로그램을 분리된 클라이언트 모듈로 구조화하게 되는데, 이 모듈들은 개별적으로 개발되고, 테스트되고, 배포될 수 있습니다. 각 모듈은 응용프로그램의 전체적인 기능의 일부를 캡슐화할 것입니다. 당신이 해야할 첫 번째 설계 결정은 응용프로그램의 기능을 분리된 모듈들로 분할하는 방식을 결정하는 것입니다.

모듈은 연관된 관심사들의 집합을 캡슐화해야 하며, 뚜렷한 책임을 지녀야 합니다. 모듈은 응용프로그램에 대한 수직적 슬라이스를 표현하거나 수평적 서비스 레이어를 표현할 수 있습니다. 큰 응용프로그램들은 비슷하게 두 종류의 모듈들을 모두 가지게 될 것입니다.

수직적 슬라이스로 조직화된 모듈을 가진 응용프로그램


수평적 레이어로 조직화된 모듈을 가진 응용프로그램


큰 응용프로그램은 수직적인 슬라이스와 수평적 레이어로 조직화된 모듈들을 가질 것입니다. 그러한 모듈들의 예는 다음을 포함합니다:

  • Stock Trader Reference Implementation( Stock Trader RI ) 의 News 모듈과 같은 특정 응용프로그램 기능을 포함하고 있는 모듈.
  • 구입, 송장, 총 계정 원장과 같은 연관된 유스케이스의 집합에 대한 특정 서브시스템이나 기능들을 포함하는 모듈.
  • 로깅, 캐싱, 권한 서비스, 웹 서비스와 같은 인프라스트럭쳐 서비스들을 포함하는 모듈.
  • 다른 내부 시스템과 더불어 Siebel CRM, SAP 와 같은 기업용 비즈니스 프로그램( line-of-business, LOB ) 시스템들을 실행하는 서비스들을 포함하는 모듈.

모듈은 다른 모듈들에 대한 최소 집합의 의존성들을 가져야 합니다. 모듈이 다른 모듈에 대한 의존성을 가지게 될 때, 그것은 콘크리트 타입 대신에 공유 라이브러리에 정의된 인터페이스를 사용하거나, EventAggregator 이벤트 타입들을 통해 다른 모듈들과 통신하기 위해서 EventAggregator 를 사용함으로써 느슨하게 결합되어야 합니다.

모듈성의 목표는 기능이나 기술이 추가되거나 삭제되더라도 응용프로그램을 유연하고, 유지보수 가능하고, 안전적인 상태로 유지하는 것이 가능한 방식으로 응용프로그램을 분할하는 것입니다. 이를 수행하기 위한 가장 최선의 방법은 모듈들이 가능한 한 독립적이고, 잘 정의된 인터페이스를 가지고, 가능한 한 고립되도록 응용프로그램을 설계하는 것입니다.

모듈들에 대한 프로젝트의 비율을 결정하기

모듈들을 생성하고 패키징하는 몇 가지 방법이 있습니다. 추천할 만하고 가장 공통적으로 사용되는 방법은 모듈당 단일 어셈블리를 생성하는 것입니다. 이는 논리 모듈들이 분리를 유지하고 적절하게 캡슐화 정도를 높이는데 도움을 줍니다. 또한 그것은 어셈블리를 모듈 경계라고 이야기하기 쉽게 만들어 줄 뿐만 아니라 모듈을 배포하는 방식에 대한 패키징을 쉽게 만들어 줍니다. 그러나, 단일 어셈블리가 다중의 모듈을 가지는 것을 막을 수는 없으며, 어떤 경우에 이는 솔루션 내의 프로젝트 개수를 최소화하기 위해서 선호되기도 합니다. 큰 응용프로그램에서, 10 개에서 50 개의 모듈들을 가지는 것은 일반적이지 않습니다. 각 모듈들을 자신의 프로젝트에 분리해 넣는 것은 솔루션에 너무 많은 복잡성을 추가하는 것이며, 비주얼 스튜디오의 성능을 저하시킬 수 있습니다. 만약 당신이 하나의 모듈당 하나의 어셈블리/비주얼 스튜디오 프로젝트로 가기로 결정했다면, 모듈이나 모듈 집합을 관리하기 위해서 자신의 솔루션에 나눠서 넣는 것도 이치에는 맞습니다.

XAP 와 모듈 팩토링

실버라이트 응용프로그램에 대해, 모듈들은 일반적으로 분리된 XAP 파일에 패키징됩니다만, 일부 경우에는 XAP 당 하나 이상의 모듈들을 가지고 있을 수 있습니다. 응용프로그램을 시작하고 새로운 기능들을 가능하게 만들기 위해서 요구되는 다운로드 요청의 횟수와 크기를 최소화하기 위해서 필요한 XAP 파일의 개수가 몇 개나 될지 고려해야 합니다. 만약 각 모듈을 자신만의 프로젝트/어셈블리로 분리하기로 했다면, 각 어셈블리를 그것들만의 XAP 에 배치할지 혹은 다중 어셈블리들을 하나의 XAP 에 포함시킬지를 결정할 필요가 있습니다.

다중 모듈들을 하나의 XAP 에 포함시킬지 그것들을 분리할지를 선택하는데 영향을 주는 요소들은 다음과 같습니다:
  • 다운로드 크기 및 공유 종속성들. 각 XAP 파일은 자신의 매니페스트 및 .zip 패키징에서 작은양의 부가적인 크기 오우버헤드를 가지게 됩니다. 또한, 모듈들 간에 공통 종속성이 존재하고 그것들이 종속적 모듈이나 캐싱된 라이브러리와 분리되어 있지 않다면, 각 XAP 는 그러한 종속적인 라이브러리들을 포함할 것이고, 이는 다운로드 크기를 심각하게 증가시킬 수도 있습니다.
  • 다중 모듈들이 응용프로그램에 의해 필요하게 될 때의 시점. 응용프로그램의 시작시에 뷰를 제출하는 것과 같이 다중 모듈들이 동시에 로드되고 사용된다면,그것들을 하나의 XAP 에 패키징하는 것이 약간 더 빠르며, 동시에 클라이언트에 대해 물리적으로 이용 가능한 상태가 되도록 보장하는데도 도움이 됩니다. 프리즘의 모듈성 기능은 종속성들을 지정하는 모듈들이 올바른 순서로 로드되는 것을 보장하며, 그래서 모듈들이 다중 XAP 들에 나뉘어 있을 때도 임포터 로드 오더가 필요하지 않도록 만들 수 있습니다. 그러나 하나가 아니라 두 개의 다운로드를 만들면 약간의 성능 오우버헤드가 존재합니다. 심지어는 전체적으로 같은 다운로드 크기를 가지고 있더라도 그렇습니다.
  • 모듈 버저닝( Versioning ). 만약 서로 다른 모듈들이 독립적인 타임라인에서 개발되고 잠재적으로 개별적으로 배포될 것이라고 하면, 당신은 그것들을 개별 XAP 들에 배치하기를 원할 것이고, 그것들은 명확하고 독립적으로 갱신되도록 하기 위해서 서로 다른 버전으로 표기될 수 있을 것입니다.

개별 XAP 파일들에서 같은 어셈블리를 중복으로 다운로드하는 것을 피하기 위한 두 가지 접근법들이 사용될 수 있습니다:

  • 공유 종속성들을 개별 인프라스트럭쳐로 분리하는 것을 고려하고, 사용되는 모듈들이 그 공유 모듈들에 대한 종속성을 가지도록 하십시오.
  • 실버라이트의 공유 라이브러리 캐싱을 사용해서 공유 타입들을 공유 라이브러리에 배치하십시오. 이는 프리즘 모듈 다운로더 대신에 실버라이트에 의해서 한 번만 다운로드되고 캐싱됩니다.


느슨한 결합을 위해 종속성 주입 사용하기


하나의 모듈은 호스트 응용프로그램이나 다른 모듈들이 제공하는 컴포넌트들과 서비스들에 의존할 것입니다. 프리즘은 모듈들 간의 종속성을 등록하는 기능을 지원하고 있어서, 그것들은 올바른 순서로 로드되고 초기화됩니다. 또한 프리즘은 모듈들이 응용프로그램에 로드될 때 초기화되도록 지원합니다. 모듈 초기화 동안에, 모듈은 그것이 요구하는 부가적인 컴포넌트들과 서비스들에 대한 참조를 획득할 수 있습니다. 그리고 다른 모듈들에서 이용 가능하게 만들기 위해서 그것이 포함하고 있는 컴포넌트들과 서비스들을 등록할 수도 있습니다.


모듈은 콘크리트 타입을 직접적으로 인스턴스화하는 대신에 외부 인터페이스의 인스턴스를 획득하기 위한 독립적인 메커니즘을 사용해야 합니다. 예를 들어, 종속성 주입 컨테이너나 팩토리 서비스를 사용할 수 있습니다. 유니티나 MEF 와 같은 종속성 주입 컨테이너들은 종속성 주입을 통해 어떤 타입이 필요로 하는 인터페이스나 타입의 인스턴스를 자동적으로 요청해 줍니다. 프리즘은 유니티와 MEF 를 통합하고 있으며, 이는 모듈들이 쉽게 종속성 주입을 사용할 수 있도록 해 줍니다.


다음 다이어그램은 컴포넌트들과 서비스들에 대한 참조를 요청하거나 등록할 필요가 있는 모듈들이 로드될 때의 일반적인 작업 과정을 보여 줍니다.


종속성 주입의 예




이 예제에서, OrdersModule 어셈블리는 OrdersRepository 클래스를 정의합니다( 이와 함께 주문 기능을 구현하는 다른 뷰들과 클래스들도 정의합니다 ). CustomersModule 어셈블리는 CustomersViewModel 클래스를 정의하는데, 이는 OrdersRepository 에 의존하며, 일반적으로 서비스에 의해 노출된 인터페이스에 의존합니다. 응용프로그램 시작과 부트스트래핑 절차는 다음과 같은 단계들을 포함합니다:

  1. 부트스트래퍼는 모듈 초기화 프로세스를 시작합니다. 그리고 나서 모듈 로더는 OrdersModule 을 로드하고 초기화합니다.
  2. OrdersModule 초기화 과정중에, 그것은 OrdersRepository 를 컨테이너에 등록합니다.
  3. 그리고 나서 모듈 로더는 CustomersModule 을 로드합니다. 모듈 로딩의 순서는 모듈 메타데이터의 종속성들에 의해 지정될 수 있습니다.
  4. CustomersModule CustomersViewModel 의 인스턴스를 컨테이너를 통해 그것을 결정( resolve )함으로써 생성합니다. 그 CustomersViewModel OrdersRepository 에 ( 일반적으로 인터페이스에 기반해 ) 종속성을 가지고 있으며, 생성자 주입이나 종속성 주입을 통해서 그것을 지정합니다. 컨테이너는 OrdersModule 에 의해 등록된 타입에 기반한 뷰 모델을 생성하는 와중에 종속성을 주입합니다. 최종 결과물은 CustomerViewModel 에서 OrderRepository 로의 인터페이스 레퍼런스이며, 두 클래스들 간에는 강한 결합이 존재하지 않습니다.
노트:
OrderRepository( IOrderRespository ) 를 노출하는 인터페이스는 분리된 "공유 서비스들" 어셈블리나 "주문 서비스들" 어셈블리에 존재할 수도 있습니다. 그 어셈블리들은 단지 서비스 인터페이스들과 그 서비스들을 노출하기 위해 요구되는 타입들 만을 포함하고 있습니다. 이러한 방식으로, CustomersModuleOrdersModule 사이에는 강한 종속성이 존재하지 않습니다.


두 모듈은 공히 묵시적인 종속성을 종속성 주입 컨테이너 상에서 가지고 있음에 주의하십시오. 이 종속성은 모듈 로더에서 모듈 생성을 하는 동안에 주입됩니다.


핵심 시나리오들



이 섹션에서는 응용프로그램에서 모듈을 사용해서 작업할 때 직면하게 될 공통적인 시나리오들에 대해 기술합니다. 이 시나리오들은 모듈 정의하기, 모듈 등록하고 검색하기, 모듈 로드하기, 모듈 초기화하기, 모듈 종속성 지정하기, 요청시 모듈 로드하기, 백그라운드로 원격 모듈 다운로드하기, 모듈이 이미 로드되었는지 확인하기 등이 포함됩니다. 코드나 XAML, 혹은 응용프로그램 구성 파일, 혹은 로컬 디렉토리 스캐닝을 통해서 모듈을 등록하고 검색할 수 있습니다.


모듈 정의하기


모듈은 개별적으로 개발되고 테스트되고 배포되고 응용프로그램에 통합될 수 있는 방식으로 패키징되는 기능 및 리소스에 대한 논리 컬렉션입니다. 각 모듈은 모듈을 초기화하고 그것의 기능을 응용프로그램에 통합하기 위한 책임이 있는 중앙 클래스를 가지고 있습니다. 이 클래스는 다음과 같이 IModule 인터페이스를 구현합니다.



Initialize 메서드를 구현하는 방법은 응용프로그램의 요구에 달려 있습니다. 모듈 클래스 타입, 초기화 모드, 그리고 모든 모듈 종속성들은 모듈 카탈로그에 정의됩니다. 카탈로그 안의 각 모듈에 대해, 모듈 로더는 모듈 클래스의 인스턴스를 생성하고, 그 다음 Initialize 메서드를 호출합니다. 모듈들은 모듈 카탈로그에서 지정된 순서로 처리됩니다. 런타임 초기화 순서는 모듈들이 다운로드되고, 이용가능해지고, 종속성들을 만족시킬 때가 언제인지에 기반합니다.


응용프로그램이 사용중인 모듈 카타로그의 타입에 기반해서, 모듈 클래스 자체의 선언적인 애트리뷰트들에 의해서 모듈 종속성들이 설정되거나, 모듈 카탈로그 파일 내부에서 모듈 종속성들이 설정될 수 있습니다. 다음 섹션들은 세부 사항들을 제공합니다.


모듈을 등록하고 검색하기


응용프로그램이 로드할 수 있는 모듈들은 모듈 카탈로그에 정의됩니다. 프리즘 모듈 로더는 모듈 카탈로그를 사용해서 응용프로그램에 로드될 수 있는 모듈이 무엇인지, 언제 그것을 로드할지, 어떤 순서로 로드할지를 결정합니다.


모듈 카탈로그는 IModuleCatalog 인터페이스를 구현하는 클래스에 의해 표현됩니다. 모듈 카탈로그 클래스는 응용프로그램 부트스트래퍼 클래스에 의해서 응용프로그램 초기화 동안에 생성됩니다. 프리즘은 모듈 카탈로그의 여러 구현을 제공하기 때문에, 여러 분은 그것을 선택할 수 있습니다. 또한 AddModule 메서드를 호출하거나 사용자 정의 행위를 가진 모듈 카탈로그를 생성하기 위해 ModuleCatalog 를 상속함으로써, 다른 데이터 소스로부터 모듈 카탈로그를 덧붙일 수 있습니다.


노트:

일반적으로, 프리즘에서의 모듈은 종속성 주입 컨테이너와 공용 서비스 로케이터를 사용해서 모듈 초기화를 위해 요구되는 타입들의 인스턴스들을 획득합니다. 유니티와 MEF 는 모두 프리즘에 의해 지원됩니다. 비록 등록, 검색, 다운로딩, 초기화의 전체 과정이 동일하지만, 그 세부사항은 유니티가 사용되느냐 아니면 MEF 가 사용되느냐에 따라서 매우 달라질 수 있습니다. 접근법들 사이의 컨테이너-지정 차이점들은 이 토픽을 통해 설명됩니다.


코드에서 모듈 등록하기


가장 기본적인 모듈 카탈로그는 ModuleCatalog 클래스에 의해 제공됩니다. 이 모듈 카탈로그를 사용해서 여러 분은 모듈 클래스 타입을 지정함으로써 모듈들을 프로그램적으로 등록할 수 있습니다. 또한 프로그램적으로 모듈 이름과 초기화 모드를 지정할 수도 있습니다. ModuleCatalog 클래스에 모듈을 직접적으로 등록하기 위해서는, AddModule 메서드를 응용프로그램의 Bootstrapper 클래스에서 호출하십시오. 그 예는 다음 코드에 나와 있습니다.



앞의 예제에서, 모듈들은 쉘에 의해 직접적으로 참조되며, 그 모듈 클래스 타입들이 정의되어, AddModule 에 대한 호출에서 사용될 수 있습니다. 이것이 예제가 모듈들을 카탈로그에 등록하기 위해 typeof( Module ) 을 사용하는 이유입니다.


노트:

만약 응용프로그램이 모듈 타입에 대한 직접 참조를 가지고 있다면, 여러 분은 위에서 보이듯이 타입에 의해 그것을 추가할 수 있습니다; 그렇지 않다면 fully qualified type name 과 어셈블리의 위치를 제공할 필요가 있습니다.


코드에서 모듈 카탈로그를 정의하는 다른 예제를 보고 싶다면, Stock Trader Reference Implementation( Stock Trader RI ) 의 StockTraderRIBootstrapper.cs 를 살펴 보십시오.


노트:

Bootstrapper 클래스는 CreateModuleCatalog 메서드를 제공해서 ModuleCatalog 의 생성을 돕도록 합니다. 기본적으로, 이 메서드는 ModuleCatalog 인스턴스를 생성합니다. 그러나 다른 유형의 모듈 카탈로그를 생성하기 위해, 상속 클래스에서 이 메서드를 재정의할 수 있습니다.


XAML 파일을 사용해서 모듈 등록하기


XAML 파일에서 모듈 카탈로그를 지정함으로써 모듈 카탈로그를 선언적으로 정의할 수 있습니다. XAML 파일은 어떤 종류의 모듈 카탈로그 클래스를 생성하고, 어떤 모듈들이 그것에 추가되어야 하는지를 지정합니다. 보통, .xaml 파일은 쉘 프로젝트의 리소스로 추가됩니다. 모듈 카탈로그는 부트스트래퍼에 의해 생성되는데, CreateFromXaml 메서드를 호출해야 합니다. 기술적인 관점에서 볼 때, 이 접근법은 코드에서 ModuleCatalog 를 정의하는 것과 매우 유사합니다. 왜냐하면 XAML 파일은 단순히 인스턴스화될 오브젝트의 계층을 정의할 뿐이기 때문입니다.


다음 코드 예제는 XAML 파일이 모듈 카탈로그를 정의하는 것을 보여 줍니다.



노트:

ModuleInfoGraoups 는 같은 .xap 파일이나 어셈블리에 존재하고, 같은 방식으로 초기화되고, 같은 그룹 내의 모듈에 대한 종속성만을 가지는 모듈들을 그룹화하는 편리한 방법을 제공합니다. 

모듈들 간의 종속성들은 같은 ModuleInfoGroup 의 모듈들 내에서 정의될 수 있습니다; 그러나, 다른 ModuleInfoGroups 내의 모듈들 사이의 종속성을 정의할 수는 없습니다.

모듈들을 모듈 그룹 내에 배치하는 것은 선택적입니다. 그룹을 위해서 설정되는 프라퍼티들은 그것이 포함하는 모든 모듈들에 적용될 것입니다. 모듈들은 그룹 내부에 존재하지 않아도 등록될 수 있다는 것도 기억하기 바랍니다.


응용프로그램의 Bootstrapper 클래스에서, XAML 파일이 ModuleCatalog 의 소스임을 지정할 필요가 있습니다. 그 예가 다음 코드에 나와 있습니다.



구성 파일을 사용해서 모듈을 등록하기


WPF 에서는, 모듈 정보를 App.config 파일에 저장하는 것이 가능합니다. 이 접근법은 이점은 이 파일이 응용프로그램으로 컴파일되지 않는다는 것입니다. 이는 응용프로그램을 재컴파일하지 않고도 런타임에 모듈을 추가하거나 삭제하는 것을 쉽게 만들어 줍니다.


다음 코드 예제는 모듈 카탈로그를 지정하는 구성 파일을 보여 줍니다. 만약 모듈이 자동으로 로드되는 것을 원한다면, startupLoaded="true" 를 설정하면 됩니다.



노트:

여러 분은 여전히 코드상에서 ConfigurationModuleCtalog 에다가 모듈들을 추가할 수 있습니다. 예를 들어, 응용프로그램이 반드시 기능하기를 바라는 모듈들이 카탈로그에 정의되도록 하기 위해서 이를 사용할 수 있습니다.


노트:

실버라이트는 구성 파일들을 사용하는 것을 지원하지 않습니다. 만약 구성파일 형식의 접근법을 원한다면, 자신만의 ModuleCatalog 를 생성해서 서버의 웹 서비스로부터 모듈 구성파일을 읽어 들이는 접근법을 추천합니다.


디렉토리에서 모듈 검색하기


WPF 에서 프리즘 DirectoryModuleCatalog 클래스는 모듈 카탈로그로 로컬 디렉토리를 지정하도록 해 줍니다. 이 모듈 카탈로그는 지정된 폴더를 스캔하고 응용프로그램을 위한 모듈을 정의하는 어셈블리들을 검색합니다. 이 접근법을 사용하려면, 모듈 이름과 그것이 가지는 모든 종속성을 지정하기 위해서 모듈 클래스에다가 선언적인 애트리뷰트를 사용할 필요가 있습니다. 다음 코드 예제는 디렉토리에서 어셈블리들을 검색해서 덧붙여진 모듈 카탈로그를 보여 줍니다.



노트:

이 기능은 실버라이트에서는 지원되지 않습니다. 왜냐하면 실버라이트 보안 모델은 어셈블리를 파일 시스템에서 로드하는 것을 허용하지 않기 때문입니다.


모듈 로드하기


ModuleCatalog 가 덧붙여지면, 모듈들이 로드되고 초기화될 준비가 된 것입니다. 모듈 로딩은 모듈 어셈블리가 디스크에서 메모리로 이동되었음을 의미합니다. 만약 어셈블리가 디스크에 존재하지 않는다면, 먼저 그걳을 획득해야만 합니다. 이것의 예는 실버라이트 .xap 파일을 사용해서 웹으로부터 어셈블리를 다운로드하는 것입니다. ModuleManager 는 로딩 및 초기화 과정을 다룰 책임이 있습니다.


모듈 초기화하기


모듈이 로드된 후에는, 그것들이 초기화됩니다. 이는 모듈 클래스의 인스턴스가 생성되고 그것의 Initialize 메서드가 호출되는 것을 의미합니다. 초기화는 응용프로그램에 모듈을 통합하는 장소입니다. 모듈 초기화를 위해 다음과 같은 가능성들을 고려하십시오:

  • 모듈의 뷰를 응용프로그램에 등록하십시오. 만약 모듈이 뷰 검색이나 뷰 주입을 사용해 유저 인터페이스( UI ) 조립하는 것이라면, 모듈은 그것의 뷰나 뷰 모델을 적절한 영역( region ) 이름과 연관시킬 필요가 있습니다. 이는 뷰가 메뉴에서 동적으로 나타날 수 있도록 해 주거나, 응용프로그램의 다른 비주얼 영역에서 나타날 수 있도록 해 줍니다.
  • 응용프로그램 레벨 이벤트나 서비스를 구독하십시오. 보통, 응용프로그램은 여러분의 모듈이 관심을 가지게 되는 응용프로그램-지정 서비스나 이벤트를 노출합니다. 모듈의 기능들을 그러한 응용프로그램-레벨 이벤트와 서비스에 추가하기 위해서는 Initialize 메서드를 사용하십시오. 예를 들어, 응용프로그램은 아마도 그것이 종료될 때나 여러분의 모듈이 그 이벤트에 반응하기를 원할 때 이벤트를 발생시킬 것입니다. 또한 여러분의 모듈은 응용프로그램 레벨 서비스에 어떤 데이터를 제공해야만 할 가능성도 있습니다. 예를 들어, 만약 MenuService( 이것은 메뉴 아이템을 추가하거나 삭제할 책임이 있습니다 )를 생성했을 때, 모듈의 Initialize 메서드가 여러분이 올바른 메뉴 아이템을 추가할 위치입니다.

노트:

모듈 인스턴스 라이프타임은 기본적으로 매우 짧습니다. 로딩 과정 동안 Initialize 메서드를 호출한 후에, 모듈 인스턴스에 대한 참조가 해제됩니다. 만약 모듈 인스턴스에 대한 강한 참조 체인을 구성하지 않는다면, 그것은 가비지 수집될 것입니다.

이 동작은 여러분이 모듈에 대한 약한 참조를 유지하는 이벤트를 구독하고 있을 때 디버깅을 어렵게 만듭니다. 왜냐하면 여러분의 모듈은 가비지 수집기가 실행될 때 이미 "사라지기" 때문입니다.

  • 종속성 주입 컨테이너에 타입들을 등록하십시오. 만약 유니티나 MEF 와 같은 종속성 주입 패턴을 사용하고 있다면, 모듈은 아마도 응용프로그램이나  사용할 다른 모듈들을 위한 타입들을 등록할 것입니다. 또한 그것은 컨테이너에 그것이 필요로 하는 타입의 인스턴스를 결정해 줄 것을 요구할 것입니다.

모듈 종속성들을 지정하기


모듈들은 다른 모듈들에 의존합니다. 만약 모듈 A 가 모듈 B 에 의존한다면, 모듈 B 는 반드시 모듈 A 전에 초기화되어야 합니다. ModuleManager 는 이러한 종속성들에 대한 트랙을 유지하며, 모듈을 적절히 초기화합니다. 여러분이 모듈 카탈로그를 정의했던 방식에 의존해, 모듈 종속성을 코드, 구성 파일, XAML 에서 정의할 수 있습니다.


코드에서 종속성들을 지정하기


코드에서 모듈을 등록하거나 디렉토리에 의해 모듈들을 검색하는 WPF 응용프로그램을 위해, 프리즘은 모듈을 생성할 때 사용하기 위한 선언적 애트리뷰트들을 제공합니다. 그 예가 아래에 나와 있습니다.



XAML 에서 종속성들을 지정하기


다음 XAML 은 모듈 F 가 모듈 E 에 의존하는 것을 보여 줍니다.



구성파일에서 종속성들을 지정하기


다음 App.config 파일 예제는 모듈 D 가 모듈 B 에 의존함을 보여 줍니다.



온디맨드로( 요청시에 ) 모듈을 로드하기


모듈을 온디맨드로 로드하려면, 모듈이 InitializeMode OnDemand 로 설정한 상태로 모듈 카탈로그에 로드되어야 합니다. 그렇게 하고 나서, 모듈이 로드될 것을 요청하는 응용프로그램 코드를 작성할 필요가 있습니다.


코드에서 온디맨드 로딩을 지정하기


모듈은 애트리뷰트를 사용해서 온디맨드로 지정되는데, 아래 코드 예제를 보십시오.



XAML 에서 온디맨드 로딩 지정하기


XAML 에서 모듈 카탈로그를 정의할 때, InitializationMode.OnDemand 를 지정할 수 있습니다. 아래 코드 예제를 보십시오.



구성파일에서 온디맨드 로딩 지정하기


App.config 파일에서 모듈 카탈로그를 정의할 때 InitializationMode.OnDemand 를 지정할 수 있습니다. 아래 코드 예제를 보십시오.



모듈의 온디맨드 로딩 요청하기


모듈이 온디맨드로 지정되면, 응용프로그램은 모듈이 로드될 것을 요청할 수 있습니다. 로딩을 초기화하기 원하는 코드는 부트스트래퍼에 의해 컨테이너에 등록된 IModuleManager 서비스에 대한 참조를 획득할 필요가 있습니다.



백그라운드로 원격 모듈들을 다운로드하기


응용프로그래 시작 후나 사용자가 모듈을 필요로 할 때 백그라운드로 모듈들을 다운로드하는 것은 응용프로그램 시작 시간을 개선해 줍니다.


원격 다운로드를 위해 모듈을 준비하기


실버라이트 응용프로그램에서, 모듈들은 .xap 파일들에 패키징됩니다. 응용프로그램으로부터 분리된 모듈을 다운로드하기 위해서는 개별적인 .xap 파일을 생성하십시오. 여러분은 다중 모듈들을 하나의 .xap 파일에 배치해서 다운로드 요청의 개수를 최적화하거나, 각 .xap 파일의 다운로드 사이즈를 최적화할 수 있습니다.


노트:

각 .xap 파일들에 대해, 새로운 실버라이트 응용프로그램 프로젝트를 생성할 필요가 있습니다. 비주얼 스튜디오 2008 과 2010 에서는, 하나의 응용프로그램 프로젝트만 .xap 파일을 만들 수 있습니다. 이런 프로젝트들 내에서 App.xaml 이나 MainPage.xaml 파일들을 만들 필요는 없을 것입니다.


다운로드 과정을 트래킹하기


ModuleManager 클래스는 응용프로그램을 위해서 모듈에 대한 다운로드 과정을 추적하기 위한 이벤트를 제공합니다. 이는 다운로드된 바이트 대 전체 받은 바이트를 제공하며, 다운로드 과정의 퍼센트도 제공합니다. 이를 사용해서 비주얼 프로그레스 인디케이터를 출력해 사용자에게 다운로드되는 과정을 보여 줄 수 있습니다.




모듈이 다 로드되었는지를 확인하기


ModuleManager 서비스는 응용프로그램을 위해서 모듈이 로드되거나 로드에 실패했을 때를 추적하기 위한 이벤트를 제공합니다. IModuleManager 인터페이스의 종속성 주입을 통해서 이 서비스에 대한 참조를 획득할 수 있습니다.




응용프로그램과 모듈의 느슨한 결합을 유지하기 위해서, 응용프로그램은 이 이벤트를 모듈을 응용프로그램에 통합하기 위한 목적으로 사용하는 것을 피해야만 합니다. 그 대신에, 모듈의 Initialize 메서드가 응용프로그램과의 통합을 다뤄야 합니다.


LoadModuleCompletedEventArgs IsErrorHandled 프라퍼티를 포함합니다. 만약 모듈을 로드하는데 실패했는데, 응용프로그램이 ModuleManager 가 에러를 로깅하거나 예외를 던지지 않기를 원한다면, 이 프라퍼티를 true 로 설정할 수 있습니다.


노트:

모듈이 로드되고 초기화된 후에, 그 모듈 어셈블리는 언로드될 수 없습니다. 모듈 인스턴스 참조는 프리즘 라이브러리에 의해 유지되지 않을 것입니다. 그래서 모듈 클래스 인스턴스는 초기화가 완료되면 가비지 수집될 것입니다.


MEF 에서 모듈들


이 섹션은 당신이 종속성 주입 컨테이너로서 MEF 를 선택했을 때의 차이점에만 집중합니다.


노트:

MEF 를 사용할 때, MefModuleManager MefBootstrapper 에 의해 사용됩니다. 이는 ModuleMnager 를 확장하고, IPartImportsSatisfiedNotification 인터페이스를 구현하여, MEF 에 의해 새로운 타입들이 임포트될 때 ModuleCatalog 가 갱신되도록 보장합니다.


MEF 를 사용하여 코드에서 모듈들을 등록하기


MEF 를 사용할 때, 여러분은 MEF 가 자동으로 타입들을 검색하도록 만들기 위해서 모듈 클래스에 ModuleExport 애트리뷰트를 적용할 수 있습니다. 다음은 그 예제입니다.



여러분은 AssemblyCatalog 클래스를 사용해서 모듈들을 검색하고 로드하도록 할 수도 있습니다. 이 클래스는 어셈블리 내에서 익스포드되는 모든 모듈 클래스들을 검색하기 위해서 사용될 수 있습니다. 그리고 AggregateCatalog 클래스는 하나의 논리 카탈로그에 다중 카탈로그들이 결합될 수 있도록 해 줍니다. 기본적으로, 프리즘 MefBootstrapper 클래스는 AggregateCatalog 인스턴스를 생성합니다. 그러면 여러분은 ConfigureAggregateCatalog 메서드를 재정의해서 어셈블리들을 등록할 수 있습니다. 그 예가 아래에 나와 있습니다.



프리즘 MefModuleManager 구현은 MEF AggregateCatalog 와 프리즘 ModuleCatalog 가 동기화되도록 유지합니다. 결국, ModuleCatalog AggregateCatalog 를 통해 추가된 모듈들을 프리즘이 검색할 수 있도록 해 줍니다.


노트:

MEF 는 Lazy<T> 를 확장해서 사용하는데, 익스포트되거나 임포트된 타입들의 초기화를 Value 프라퍼티가 사용되기 전까지 지연시키기 위해서입니다.


MEF 를 사용하여 디렉토리 내의 모듈들을 검색하기


MEF 는 모듈들을 포함하고 있는 어셈블리들( 그리고 다른 MEF 익스포트 타입들 )을 위해 디렉토리를 검색하는 데 사용되는 DirectoryCatalog 를 제공합니다. 이 경우, 여러분은 ConfigureAggregateCatalog 메서드를 재정의햇, 디렉토리를 등록하게 됩니다. 이 접근법은 WPF 에서만 이용 가능합니다.


이 접근법을 사용하기 위해서는, 먼저 ModuleExport 애트리뷰트를 사용해 모듈에 모듈 이름들과 종속성들을 적용할 필요가 있습니다. 아래 코드에 그 예가 나와 있습니다. 이는 MEF 가 모듈들을 임포트할 수 있도록 해 주고, 프리즘이 ModuleCatalog 를 갱신할 수 있도록 해 줍니다.



MEF 를 사용해 코드에서 종속성들을 지정하기


MEF 를 사용하는 WPF 응용프로그램에 대해, ModuleExport 애트리뷰트를 아래와 같이 사용하십시오.



MEF 는 런타임에 모듈들을 검색하도록 해 주기 때문에, 모듈들 간의 새로운 종속성도 런타임에 검색될 수 있습니다. ModuleCatalog 를 MEF 와 함께 사용할 수 있기는 하지만, ModuleCatalog 는 XAML 이나 구성 파일로부터 그것이 로드될 때 ( 모듈이 로드되기 전에 ) 종속성 체인에 대한 유효성 검사를 수행한다는 것을 기억하는 것이 중요합니다. 만약 ModuleCatalog 에 모듈이 열거되어 있고 MEF 를 사용하여 로드된다면, ModuleCatalog 종속성들이 사용될 것이며, DependsOnModuleNames 애트리뷰트가 무시될 것입니다. 개별 XAP 파일에 모듈들을 가지고 있는 실버라이트 응용프로그램에서는 ModuleCatalog 와 함께 MEF 를 사용하는 것이 가장 일반적입니다.


MEF 를 사용해 온디맨드 로딩을 지정하기


MEF 를 사용하고 모듈들과 모듈 종속성들을 지정하기 위해서 ModuleExport 애트리뷰트를 사용하고 있다면, 온디맨드로 로드되어야 하는 모듈을 지정하기 위해서 InitializationMode 프라퍼티를 사용할 수 있습니다. 다음 예제를 보십시오.



MEF 를 사용해 원격 다운로드를 위한 모듈을 준비하기


내부적으로 MEF 를 사용하는 프리즘 응용프로그램은 MEF 의 DeploymentCatalog 클래스를 사용하여 .xap 파일들을 다운로드하고 그 .xap 파일들 내의 어셈블리들과 타입들을 검색합니다. MefXapModuleTypeLoader AggregateCatalog 에 각 DeploymentCatalog 를 추가합니다.


두 가지 다른 .xap 파일들이 추가되고 같은 공유 어셈블리들을 포함한다면, 같은 타입들이 다시 임포트됩니다. 이는 타입이 싱글톤으로 지정되었는데 모듈들 간에 공유되는 어셈블리에 존재할 때 recomposition 에러를 발생시킬 수 있습니다. Microsoft.Practices.Prism.MefExtensions.dll 은 그런 어셈블리의 예입니다.


중복 임포트를 막기 위해서는, 각 모듈 프로젝트의 참조를 열고, 그러한 공유 DLL 들에 대해 'Copy Local'=false 로 설정하십시오. 이는 모듈의 .xap 파일에 그 어셈블리가 패키징되는 것을 막으며, 다시 임포트되는 것을 막습니다. 또한 각 .xap 파일의 전체적인 크기도 줄여 줍니다. 이를 위해서, 모듈 .xap 파일이 다운로드되기 전에, 응용프로그램이 공유 어셈블리를 참조하거나 응용프로그램이 공유 어셈블리를 포함하는 .xap 파일을 다운로드하는 것을 보장할 필요가 있습니다.


더 많은 정보들



어셈블리 캐싱에 대한 더 많은 정보를 원한다면, MSDN 의 "방법: 어셈블리 캐싱 사용하기" 를 참조하심시오: http://msdn.microsoft.com/en-us/library/dd833069(VS.95).aspx


프리즘의 모듈성에 대한 더 많은 정보를 원한다면, Modularity with MEF for WPF QuickStart 나 Modularity with Unity for WPF QuickStart 를 참조하십시오. QuickStart 에 대한 더 많은 정보를 원한다면, 도움말의 Modularity QuickStarts for WPF 를 참조하십시오.


프리즘 라이브러리에서 확장될 수 있는 모듈성 기능들에 대한 더 많은 정보를 원한다면, 도움말에서 "Extending Prism" 의 "Modules" 를 참조하십시오.


'Programming > Prism4.1' 카테고리의 다른 글

진보된 MVVM 시나리오들  (0) 2014.09.20
MVVM 패턴 구현하기  (0) 2014.09.16
컴포넌트 간의 종속성 관리하기  (0) 2014.09.10
프리즘 응용프로그램 초기화하기  (0) 2014.08.24
왜 프리즘을 사용하는가?  (0) 2014.08.19
소개  (0) 2014.08.18
프리즘 4.1 설치  (0) 2014.08.14

도움말 번역입니다.



컴포넌트 간의 종속성 관리하기



프리즘 라이브러리에 기반하는 응용프로그램들은 복합 응용프로그램으로, 잠재적으로 많은 느슨하게 결합된 타입들과 서비스들로 구성되어 있습니다. 그것들은 사용자 동작에 기반한 통지를 획득하고 칸텐트에 기여하기 위해서 상호작용할 필요가 있습니다. 그것들은 느슨하게 결합되어 있기 때문에, 요구되는 비즈니스 기능들을 전달하기 위해서 서로 상호작용하고 통신하는 방법을 필요로 합니다.


이 다양한 조각들을 서로 묶기 위해서, 프리즘 라이브러리에 기반한 응용프로그램들은 의존성 주입 컨테이너( dependency injection container )에 의존합니다. 의존성 주입 컨테이너들은 오브젝트 사이의 종속성 결합을 줄여 주는데, 이는 클래스들의 인스턴스들을 생성하는 기능과 컨테이너의 구성에 기반해 그것들의 라이프타임을 관리하는 기능을 제공함으로써 이루어집니다. 오브젝트 생성 동안에, 컨테이너는 오브젝트가 요구하는 모든 종속성들을 주입합니다. 만약 그 종속성들이 아직 생성되지 않았다면, 컨테이너가 먼저 종속성들을 생성하고 결정합니다. 어떤 경우에는, 컨테이너 자체가 종속성으로서 결정될 수 있습니다. 예를 들어, 유니티 응용프로그램 블락( Unity )을 컨테이너로 사용하고 있을 때, 모듈들은 주입된 컨테이너를 가지게 되므로, 그것들은 자신의 뷰와 서비스를 컨테이너에 등록할 수 있습니다.


컨테이너를 사용하는 것은 몇 가지 이점을 지닙니다:

  • 컨테이너는 컴포넌트가 자신의 종속성의 위치를 결정하고 그것의 라이프타임을 관리하지 않도록 해 줍니다.
  • 컨테이너는 컨포넌트에 영향을 주지 않고 구현된 종속성들이 교체되는 것을 허용합니다.
  • 컨테이너는 종속성들을 가상화( mock )하는 것을 허용함으로써 테스트를 용이하게 합니다.
  • 컨테이너는 새로운 컴포넌트들이 시스템에 쉽게 추가될 수 있도록 함으로써 유지보수성을 증가시킵니다.


프리즘 라이브러리에 기반한 응용프로그램의 문맥에서는, 컨테이너에 대한 특별한 이점들이 존재합니다:

  • 컨테이너는 모듈이 로드될 때 마쥴 종속성들을 마쥴에 주입합니다.
  • 컨테이너는 뷰 모델과 뷰를 등록하고 결정하기 위해서 사용됩니다.
  • 컨테이너는 뷰 모델을 생성하고 뷰를 주입할 수 있습니다.
  • 컨테이너는 영역 관리자와 이벤트 어그리게이터와 같은 복합 서비스들을 주입합니다.
  • 컨테이너는 모듈-지정 서비스들을 등록하는데 사용됩니다. 그 서비스는 모듈-지정 기능을 가지고 있습니다.

노트:

프리즘 가이던스의 일부 샘플들은 컨테이너로 유니티 응용프로그램 블락에 의존합니다.다른 코드 샘플들은, 예를 들어 Modularity QuickStarts 와 같은 경우에는, 관리되는 확장성 프레임워크( MEF )를 사용합니다. 프리즘 라이브러리 자체는 컨테이너-지정이 아닙니다. 그리고 그것의 서비스들과 패턴들을 다른 컨테이너와 함께 사용할 수 있습니다. Castle Windsor, StructureMap, Spring.NET 와 같은 컨테이너들이 있습니다.


주요 결정: 의존성 주입 컨테이너 선택하기



프리즘 라이브러리는 의존성 주입 컨테이너를 위한 두 가지 옵션을 제공합니다: Unity 와 MEF. 프리즘은 확장성이 있기 떄문에, 약간 다른 작업을 수행하는 다른 컨테이너들이 대신 사용되는 것을 허용합니다. Unity 와 MEF 는 모두 의존성 주입을 위한 같은 기본 기능을 제공합니다. 그것들이 매우 다르게 동작하고 있음에도 불구하고 말이죠. 두 컨테이너는 다음을 포함하는 일부 기능들을 제공합니다:

  • 그것들은 모두 컨테이너에 타입들을 등록합니다.
  • 그것들은 모두 등록된 타입들의 인스턴스들을 생성합니다.
  • 그것들은 모두 등록된 타입들의 인스턴스들을 생성자에 주입합니다.
  • 그것들은 모두 등록된 타입들의 인스턴스들을 프라퍼티들에 주입합니다.
  • 그것들은 모두 관리될 필요가 있는 타입들과 종속성들을 표시하기 위해서 선언적인 애트리뷰트들을 가집니다.
  • 그것들은 모두 오브젝트 그래프에서 종속성들을 결정합니다.


Unity 는 MEF 가 제공하지 않는 몇 가지 기능들을 제공합니다:

  • 그것은 등록없이 concrete 타입들을 결정합니다.
  • 그것은 open generics 를 결정합니다.
  • 그것은 오브젝트들에 대한 호출을 캡춰하고 대상 오브젝트들에 기능을 추가하기 위해서 인터셉션( interception )을 사용합니다.


MEF 는 Unity 가 제공하지 않는 몇 가지 기능들을 제공합니다:

  • 그것은 디렉토리에서 어셈블리를 검색합니다.
  • 그것은 XAP 파일 다운로드와 어셈블리 검색을 사용합니다.
  • 그것은 속성과 컬렉션을 발견된 새로운 타입들로 재구성합니다.
  • 그것은 자동으로 상속된 타입들을 익스포트합니다.
  • 그것은 닷넷 프레임워크와 함께 배포됩니다.


이 컨테이너들은 기능과 동작에서 차이점들을 가지고 있습니다. 그러나 프리즘 라이브러리는 둘 다 컨테이너로 작동하게 만들며, 유사한 기능을 제공할 것입니다. 어떤 컨테이너를 사용할 것인지를 고려할 때는, 앞에서 언급한 기능들을 살펴 보고 자신의 시나리오에 잘 맞는 것을 선택하십시오.


컨테이너 사용시 고려 사항들


컨테이너를 사용하기 전에 다음을 고려해야 합니다:

  • 컨테이너를 사용할 때 컴포넌트를 등록하고 결정하는데 어떠한 것이 적절한지를 고려하십시오 :
    • 컨테이너에 등록하고 인스턴스를 컨테이너로부터 결정하는 것의 성능 부하가 시나리오에서 받아들여질 만한 것인지를 고려하십시오. 예를 들어, 만약 렌더링 메서드의 로컬 영역 안에서 서피스를 그리기 위해 만 개의 폴리곤을 생성할 필요가 있다고 할 때, 모든 폴리곤 인스턴스들을 컨테이너를 통해서 결정하는 비용은 엉청난 성능 부하를 가지게 될 것입니다. 왜냐하면 컨테이너가 각 엔터티를 생성하기 위해서 리플렉션을 사용하기 때문입니다.
    • 종속성들이 많거나 깊다면, 생성 비용은 엄청나게 증가할 수 있습니다.
    • 컴포넌트가 종속성을 가지고 있지 않거나 다른 타입에 대한 종속성이 아니라면, 그것을 컨테이너에 배치하는 것은 불필요합니다.
    • 컴포넌트가 타입에 대해 필수적이며 결코 변하지 않는 단일한 종속성 집합을 가진다면, 그것을 컨테이너에 배치하는 것은 불필요합니다.
  • 컴포넌트의 라이프타임이 싱글톤으로 등록되어야 하는지 인스턴스로 등록되어야 하는지를 고려하십시오:
    • 컴포넌트가 로깅 서비스와 같이 단일 리소스를 위한 리소스 관리자로서 동작하는 전역 서비스라면, 그것을 싱긆톤으로 등록하는 것을 원할 것입니다.
    • 컴포넌트가 다수의 고객에 대한 공유 상태를 제공한다면, 그것을 싱글톤으로 등록하는 것을 원할 것입니다.
    • If the object that is being injected needs to have a new instance of it injected each time a dependent object needs one, register it as a non-singleton. 예를 들어, 각 뷰는 아마도 뷰 모델의 새로운 인스턴스를 필요로 할 것입니다.
  • 코드를 통해 구성되기를 원하는지 구성( configuration )을 통해 구성되기를 원하는지를 고려하십시오 :
    • 서로 다른 모든 서비스들을 중앙에서 관리하고자 한다면, 구성 파일을 통해 컨테이너를 구성하십시오.
    • 개별 서비스들을 선택적으로 등록하기를 원한다면, 코드를 통해 컨테이너를 구성하십시오.
    • 모듈-레벨 서비스들을 가지고 있다면, 코드를 통해 컨테이너를 구성하는 것을 고려하십시오. 그러면 그 서비스들은 모듈이 로드될 때만 등록됩니다.


노트:

MEF 와 같은 어떤 컨테이너들은 구성 파일을 통해서 구성될 수 없으며, 반드시 코드를 통해 구성되어야만 합니다.



핵심 시나리오들



컨테이너들은 두 가지 주요 목적을 위해서 사용됩니다. 말하자면 등록( registering )과 결정( resolving )입니다.


등록


종속성을 오브젝트에 주입하기 전에, 종속성들의 타입이 컨테이너에 등록될 필요가 있습니다. 타입을 등록하는 것은 보통 컨테이너에 인터페이스와 인터페이스를 구현하는 concrete 타입을 넘기는 것을 포함합니다. 타입과 오브젝트를 등록하기 위한 두 가지 주요 수단이 존재합니다: 코드를 통하거나 구성 파일을 통하는 것입니다. 특정 수단은 컨테이너 별로 다양합니다.


보통, 코드를 통해 컨테이너에 타입과 오브젝트를 등록하는 것은 두 가지 방식이 있습니다:

  • 컨테이너에 타입이나 매핑을 등록할 수 있습니다. 적절한 시점에, 컨테이너는 당신이 지정하는 타입의 인스턴스를 빌드할 것입니다.
  • 현존하는 오브젝트 인스턴스를 싱글톤으로 컨테이너에 등록할 수 있습니다. 이 컨테이너는 현존하는 오브젝트의 레퍼런스를 반환할 것입니다.


유니티 컨테이너에 타입을 등록하기


초기화 동안에, 타입은 뷰나 서비스와 같은 다른 타입들을 등록할 수 있습니다. 등록은 그것들의 종속성들이 컨테이너를 통해서 제공될 수 있도록 허용하며, 그것들이 다른 타입들로부터 접근 가능하도록 허용합니다. 이를 수행하기 위해서, 타입은 모듈 생성자에 주입된 컨테이너를 가지고 있을 필요가 있습니다. 다음 코드는 Commanding Quickstart 에 있는 OrderModule 타입이 타입을 등록하는 방법에 대해서 보여 줍니다.



어떤 컨테이너를 사용하느냐에 따라, 등록은 구성 파일을 통해 코드 외부에서 수행될 수도 있습니다. 예를 들어, 4장의 "Registering Modules using a Configuration File" 의 "Modulear Application Development" 를 참조하십시오.


노트:

구성 파일에 등록하는 것과 비교했을 때 코드에서 등록하는 것의 이점은 등록이 모듈이 로드될 때만 발생한다는 것입니다.


MEF 를 사용하여 타입을 등록하기


MEF 는 애트리뷰트 기반 시스템을 사용해서 컨테이너에 타입을 등록합니다. 결과적으로, 타입 등록을 컨테이너에 추가하는 것이 단순합니다: 그것은 부가적인 [Export] 애트리뷰트를 타입에 요규합니다. 다음 코드에서 그 예를 볼 수 있습니다.



MEF 를 사용할 때의 또 다른 옵션은 클래스의 인스턴스를 생성하고 개별 인스턴스를 컨테이너에 등록하는 것입니다. Modularity for Silverlight with MEF QuickStart 의 QuickStartBootstrapper ConfigureContainer 메서드에서 이 예를 보여 줍니다.



노트:

MEF 를 컨테이너로 사용할 때, 타입들을 등록하기 위해서 애트리뷰트들을 사용하는 것을 권장합니다.


결정


타입이 등록되면, 그것은 종속성으로서 결정되거나 주입될 수 있습니다. 타입이 결정되고 있을 때, 컨테이너는 새로운 인스턴스를 생성할 필요가 있습니다. 그것은 이 인스턴스들에 종속성들을 주입합니다.


일반적으로, 타입이 결정될 때, 세 가지 일 중에 하나가 발생합니다:

  • 타입이 등록되지 않았다면, 컨테이너는 예외를 던집니다.
노트:
유니티를 포함하는 일부 컨테이너들은 등록된 적이 없는 콘크리트 타입을 결정하는 것을 허용합니다.
  • 타입이 싱글톤으로 등록되었다면, 컨테이너는 싱글톤 인스턴스를 반환합니다. 그 타입이 처음으로 호출되는 것이라면, 컨테이너는 그것을 생성하고 이후의 호출을 위해서 저장합니다.
  • 타입이 싱글톤으로서 등록되지 않았다면, 컨테이너는 새로운 인스턴스를 반환합니다.
노트:
기본적으로, MEF 에 등록된 타입들은 싱글톤들이며, 컨테이너는 그 오브젝트에 대한 참조를 저장합니다. 유니티에서는, 오브젝트들의 새로운 인스턴스들이 기본적으로 반환되며, 컨테이너는 오브젝트에 대한 참조를 유지하지 않습니다.


유니티를 사용해 인스턴스를 결정하기


다음 코드 예제는 Commanding QuickStart 에서 가져 온 것으로, OrdersEditorView OrdersToolBar 를 해당 영역과 연관시키기 위해서 컨테이너로부터 결정되는 곳이 어디인지를 보여 줍니다.



OrderEditorPresentationModel 생성자는 다음 종속성들을 포함하는데( 주문 저장소와 주문 명령 프락시 ), 그 종속성들은 그것이 결정될 때 주입됩니다..



이전 코드에서 본 생성자 주입과 함께, 유니티는 프라퍼티 주입도 허용합니다. [Dependency] 애트리뷰트가 적용된 모든 프라퍼티들은 오브젝트가 결정될 때 자동적으로 결정되고 주입됩니다.


MEF 를 사용해 인스턴스를 결정하기


다음 코드 예제는 Modularity for Silverlight with MEF QuickStart 에 있는 Bootstrapper 가 쉘의 인스턴스를 획득하는 방법을 보여 줍니다. 콘크리트 타입을 요청하는 대신, 코드는 인터페이스의 인스턴스를 요청할 것입니다.



MEF 에 의해서 결정되는 모든 클래스에서, 생성자 주입을 사용할 수도 있습니다. 그 예가 Modularity for Silverlight with MEF QuickStart 에 있는 ModuleA 인데, 그것은 주입된 ILoggerFacade IModuleTracker 를 가지고 있습니다.



또 다른 옵션은 프라퍼티 주입을 사용하는 것입니다. 그 예는 Modularity for Silverlight with MEF QuickStart 에 있는 ModuleTracker 인데, 그것은 주입된 ILoggerFacade 의 인스턴스를 가지고 있습니다.



노트:

실버라이트에서, 임포트된 프라퍼티들과 필드들은 반드시 퍼블릭이어야만 합니다.


프리즘에서 종속성 주입 컨테이너들과 서비스들을 사용하기



종속성 주입 컨테이너들은, 보통 그냥 "컨테이너"라고 불리는데, 컴포넌트들 간의 종속성을 만족시키기 위해서 사용됩니다; 이 종속성들을 만족시키는 것은 일반적으로 등록과 결정을 포함합니다. 프리즘 라이브러리는 유니티 컨테이너와 MEF 컨테이너를 지원하는데, 그것은 컨테이너-지정은 아닙니다. 이 라이브러리는 IServiceLocator 인터페이스를 통해서 컨테이너에 접근하기 때문에, 컨테이너는 대체될 수 있습니다. 이를 위해서는 당신의 컨테이너가 IServiceLocator 인터페이스를 구현해야만 합니다. 보통, 컨테이너를 대체하고 있을 때, 자신만의 컨테이너-지정 부트스트래퍼를 제공할 필요가 있을 것입니다. IServiceLocator 인터페이스는 Common Service Locator Library 에서 정의됩니다. 이는 종속성 주입 컨테이너나 서비스 로케이터와 같은 IoC( Inversion of Control ) 컨테이너들에 대한 추상을 제공하기 위한 오픈 소스 작업입니다. 이 라이브러리를 사용하는 목적은 특별한 구현에 얽매이지 않고 IoC 와 Service Location 을 위한 지렛대를 제공하기 위함입니다.


프리즘에서 Common Service Locator 구현들



비록 프리즘 라이브러리가 특정 컨테이너를 참조하거나 의존하지 않는다지만, 응용프로그램은 특정 컨테이너에 의존하는 것이 일반적입니다. 이는 특정 응용프로그램이 그 컨테이너를 참조하는 것이 합리적이지만, 프리즘 라이브러리가 컨테이너를 직접적으로 참조하지는 않는다는 것을 의미합니다. 예를 들어, 프리즘에 포함된 StockTrader RI 와 몇 몇 QuickStart 들이 유니티를 컨테이너로 사용합니다. 다른 샘플들과 QuickStart 들은 MEF 를 사용합니다.


IServiceLocator



다음 코드는 IServiceLocator 인터페이스를 보여 줍니다.



서비스 로케이터는 다음 코드에서 보여 주는 확장 메서드들을 사용해서 프리즘 라이브러리 내에서 확장됩니다. IServiceLocator 가 결정을 위해서만 사용되는 것을 볼 수 있으며, 그것은 인스턴스 획득을 위해서 사용됨을 의미합니다; 등록을 위해서 사용되지는 않습니다.



TryResolve 확장 메서드 - 유니티 컨테이너는 지원하지 않음 - 는 결정하려고 하는 이미 등록된 타입의 인스턴스를 반환합니다; 등록되지 않았다면, null 을 반환합니다.


모듈이 로드되는 동안, ModuleInitializer 는 모듈을 결정하기 위해서 IServiceLocator 를 사용합니다. 그 예가 아래에 나와 있습니다.




IServiceLocator 를 사용하는데 고려할 점들



IServiceLocator 는 범용-목적 컨테이너를 위한 수단이 아닙니다. 컨테이너들은 다양한 sematics of usage 를 가지며, 그것은 보통 그 컨테이너가 선택된 이유가 무엇인지를 결정하게 됩니다. 이를 염두에 두고, Stock Trader RI 는 IServiceLocator 를 직접적으로 사용하는 대신에 유니티를 사용합니다. 이는 응용프로그램 개발에 있어서 추천되는 접근법입니다.


다음 상황에서는, IServiceLocator 를 사용하는 것이 적합할지도 모릅니다:

  • 당신이 다중 컨테이너를 지원할 필요가 있는 서드-파티 서비스를 설계하는 독립적인 소프트웨어 벤더( ISV, independent software vendor )인 경우.
  • 당신이 다중 컨테이너를 사용할 수도 있는 조직에서 사용될 서비스를 설계하고 있는 경우.


더 많은 정보들



컨테이너와 관련한 더 많은 정보들을 원한다면, 아래 문서들을 참조하십시오:


'Programming > Prism4.1' 카테고리의 다른 글

진보된 MVVM 시나리오들  (0) 2014.09.20
MVVM 패턴 구현하기  (0) 2014.09.16
모듈러 응용프로그램 개발  (0) 2014.09.13
프리즘 응용프로그램 초기화하기  (0) 2014.08.24
왜 프리즘을 사용하는가?  (0) 2014.08.19
소개  (0) 2014.08.18
프리즘 4.1 설치  (0) 2014.08.14



'ETC > Digital Painting' 카테고리의 다른 글

미사일  (0) 2016.04.07
물고기2  (0) 2015.10.15
물고기  (0) 2015.10.14
고양이 뼈대와 몸  (0) 2014.09.03
고양이 근육 연습  (0) 2014.08.31
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18



'ETC > Digital Painting' 카테고리의 다른 글

미사일  (0) 2016.04.07
물고기2  (0) 2015.10.15
물고기  (0) 2015.10.14
인간 뼈대  (0) 2014.09.09
고양이 근육 연습  (0) 2014.08.31
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18

근육 연습. 디테일을 하다가 지겨워서 중단.





'ETC > Digital Painting' 카테고리의 다른 글

미사일  (0) 2016.04.07
물고기2  (0) 2015.10.15
물고기  (0) 2015.10.14
인간 뼈대  (0) 2014.09.09
고양이 뼈대와 몸  (0) 2014.09.03
고양이 뼈 연습 및 사이트 링크  (0) 2014.08.26
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18

캣 아나토미라는 고마운 사이트가 있어서 보고 연습함. http://www.familyvet.com/Cats/indexPG.html




'ETC > Digital Painting' 카테고리의 다른 글

물고기2  (0) 2015.10.15
물고기  (0) 2015.10.14
인간 뼈대  (0) 2014.09.09
고양이 뼈대와 몸  (0) 2014.09.03
고양이 근육 연습  (0) 2014.08.31
고양이과 골격 연습 측면  (0) 2014.08.24
얼굴 일부  (0) 2014.04.14
프로도????  (0) 2014.04.05
사람연습  (0) 2014.03.18
간만에 구연습  (0) 2014.02.04

+ Recent posts