카테고리 없음

[Translation] Silverlight Model-View-ViewModel Pattern 2

wafe 2008. 11. 16. 00:17

Silverlight Model-View-ViewModel Pattern 1에 이어서.



눈치챘을지도 모르겠지만, 이 코드는 의존성 주입(dependency injection)의 필요성을 외치고 있고, 내가 다음 블로그 포스트에서 다루려고 하는 게 바로 그것이다. 우리는 현재 어플리케이션 개체의 타입을 체크한다. Blend는 그것 스스로의 어플리케이션 개체를 제공하는 반면, 실제로 실행되는 어플리케이션은 우리가 만든 어플리케이션 개체를 우리에게 준다.

리팩토링이 필요한 다음 부분은 메인 사용자 인터페이스이다. 여기서 내가 말하는 것은 카드 주위의 모든 것이다. 어플리케이션은 Twitter 계정을 넣는 텍스트 상자 하나와 새 카드를 추가하기 위한 추가 버튼을 가지고 있다. 클릭 이벤트 핸들러에서 우리는 새 YouCard 사용자 컨트롤의 인스턴스를 만들어서 스택 패널에 넣는다. 또한 YouCard 컨트롤에서 발생하는 클로즈 이벤트를 리스닝하는 이벤트 핸들러를 작성한다. 이런 접근 방법은 몇 가지 이유 때문에 좋지 않은 방법이다. 주요한 문제는 너무 많은 행동(behavior)과 로직을 뷰(XAML 페이지의 코드 비하인드)에 넣는 것이다. 예를들어 디자이너는 코드를 수정하지 않고서는 스택 패널에서 플로우 패널로 바꿀 수가 없다. 또한 어플리케이션 로직의 너무 많은 부분이 특정 UI 컨트롤과 UI 이벤트에 종속되어 있어 코드를 유닛 테스트하기가 어렵다.

이런 문제를 해결하기 위해서 우리는 YouCardData 개체의 옵저버블 컬렉션(Users)을 포함하는 새로운 뷰-모델을 만들 것이다. 이 컬렉션은 YouCard 사용자 컨트롤을 데이터 템플릿으로 사용하는 items presenter control에 바운드된다.


이제 우리는 뷰-모델에 바운드된 UI들을 가지고, 데이터를 어떻게 표시할 것이지 콘트롤하게 되었다. 하지만 이것은 아직 절반 정도밖에 안된다. 아직 사용자가 카드를 추가/삭제할 때 뷰-모델과 인터랙션하게 할 방법이 필요하다.

YouCard 어플리케이션에는 뷰-모델에 영향을 주는 인터랙션 포인트가 두 군데 있는데, 텍스트 상자와 새 사용자 추가 버튼, 목록에서 카드를 제거하는 빨간 버튼이 그것이다. 사용자는 사용자 이름을 입력하고 엔터 키를 누르거나, 추가 버튼을 클릭하여 Users 컬렉션에 새 아이템을 추가한다. 추가 버튼은 입력된 사용자 이름이 올바른 경우에만 활성화되어야 한다. 사용자가 카드에 있는 닫기 버튼을 클릭하면 우리는 뷰-모델에 있는 컬렉션으로부터 그 아이템을 제거해야 한다.

가장 뻔한 해답은 추가 버튼에 클릭 이벤트 리스너를 추가해주고, 새 사용자를 추가하기 위해서 뷰-모델에 있는 메서드를 호출하는 방법일 것이다. 우리는 텍스트 상자의 text changed 이벤트를 받아서 입력을 검증하고 텍스트에 따라서 텍스트 상자를 활성화/비활성화 할 수 있을 것이다. 또한 우리는 빨간 버튼을 클릭했을 때 발생하는 이벤트를 YouCard 컨트롤에 추가할 수도 있을 것이다. 메인 뷰는 이 이벤트를 받아서 해당하는 카드를 뷰-모델로부터 제거할 수 있을 것이다. 이 방법의 문제점은 디자이너가 소유해야만 하는 뷰에 또 다시 로직과 행동(behavior)을 넣게 된다는 것이다. 그렇게 되면 검증 규칙 같은 것들과 추가되거나 제거된 사용자를 로컬 컴퓨터의 isolated storage에 저장하는 것(우리 어플리케이션은 당신이 추가한 카드들을 저장한다) 같은 일들을 유닛 테스트하기가 더 힘들어진다.

뷰와 뷰-모델 간의 인터랙션 문제를 해결하기 위해서 우리는 커맨드 패턴을 적용할 것이다. 커맨드 패턴을 사용하면 액션을 커맨드 개체로 캡슐화할 수 있게 된다. 커맨드 개체는 보통 실행 메서드, 이름과 설명, 커맨드가 활성화되었는지 아닌지에 대한 몇몇 정보를 포함한다. 하나의 커맨드 개체가 여러 UI 엘리먼트에 붙을 수 있다. 당신은 버튼, 키보드 단축키, 메뉴 아이템으로부터 open-file-command를 호출하고 싶을 지도 모르겠다. WPF는 Commands에 대한 지원을 내장하고 있지만 Silverlight 2에는 포함되지 않았다.

Nikhil은 Silverlight behavior를 통해서 어떻게 커맨드 같은 기능을 만들 수 있는지에 대해서 멋진 아이디어를 소개했다. 그것은 ASP.NET 컨트롤을 AJAX behavior로 확장하는 데 사용된 것과 같은 개념이다. 첫번째 ViewModel 포스트에서 Nikhil은 XAML에서 커맨드를 호출하는 데에 다음과 같은 문법을 사용했다.


첨부 속성(attached property)를 이용해서, 뷰-모델에 있는 Search 메서드에 검색 텍스트 상자의 text 속성을 인자로 전달하면서 메서드를 호출하는 행동을 버튼에 추가했다. 이어지는 포스트에서 Nikhil은 Dynamic Language Runtime을 사용하여 더욱 컴팩트한 문법을 사용하는 방법을 보여주었다.

이 접근 방법의 멋진 점은 첨부된 클릭 이벤트에 모든 동적 언어 표현을 사용할 수 있다는 점이다. 메서드 호출이나 페이지에 있는 다른 엘리먼트로부터 파라미터를 얻는 것 등을 사용할 수 있는 것이다. 이 방법으로 뷰를 뷰-모델에 연결하게 되면 놀라운 유연성을 얻게 된다. 이 방법의 문제점은 동적 언어 런타임에 의존성이 생겨서 Silverlight 어플리케이션 크기가 늘어나게 된다는 것이다. 하지만 내가 더 중요하게 생각하는 부분은 이 방법을 쓰게 되면 Blend 2.5에서 제공하는 디자인 타임 지원을 사용할 수 없다는 점이다. 따라서 우리는 다른 접근법을 찾아야 한다.

나는 CodePlex에 있는 "Silverlight Extensions" 프로젝트에서 발견한 좀더 고전적인 커맨드 패턴 구현을 사용하기로 했다. 그 프로젝트는 컨트롤, 헬퍼 클래스, 확장 메서드 등을 포함하고 있다. 우리는 커맨드 패턴 구현에만 관심이 있으므로, 구현 클래스들을 YouCard 프로젝트로 옮기기로 했다. 이제 텍스트 상자와 버튼의 XAML 코드는 아래와 같다.

텍스트 상자는 뷰-모델의 Username 속성에 바운드된다. 두 컨트롤은 모두 뷰-모델의 Username을 파라미터로 사용하여 AddCard 커맨드를 호출한다.

커맨드는 다음과 같이 정의된다.

정적 Commands 클래스는 우리 어플리케이션에서 사용할 수 있는 모든 커맨드에 대한 참조를 유지한다. 새 Command 개체 인스턴스를 만들 때 그것은 Command 개체에 있는 정적 사전(static dictionary)에 추가된다. 어플리케이션에서 만들어진 모든 커맨드 개체는 그 사전에 캐싱된다. XAML에서 CommandService attribute를 사용할 때 CommandService 클래스는 CommandSubscription 클래스를 사용하여 올바른 커맨드를 가진 UI 엘리먼트에 연결된다. 사용자가 사용자 추가 버튼을 클릭했을 때 혹은 텍스트 상자에서 엔터 키를 입력했을 때, CommandSubscription 클래스는 UI 이벤트를 받아서 해당하는 Command 개체의 Executed 이벤트를 발생시킨다. 커맨드가 실행되었을 때 무언가 행동을 취하기를 원하는 클래스는 단순히 Command 개체의 Executed 이벤트를 받기만 하면 된다. 지금같은 경우 우리는 뷰-모델 클래스에서 카드를 추가하고 삭제하는 작업을 처리하기를 원한다.

얘기할만한 가치가 있는 다른 부분으로는 사용자 추가 버튼을 활성화/비활성화하는 부분이 있다. 이 부분은 IsEnabled 버튼의 속성을 뷰-모델의 IsAddEnabled 속성에 데이터 바인딩하는 식으로 되어있다. IsAddEnabled 속성은 이렇게 되어있다.

텍스트 상자는 IsAddEnabled 속성에 대한 PropertyChanged 이벤트를 발생시키는 Username 속성에 바운드되어 있다. 속성의 get 부분에서 우리는 버튼이 활성화되어야 하는지 아닌지에 대한 검증 규칙을 적용한다.

이 포스트가 Model-View-ViewModel 패턴이 어떻게 UI로부터 가능한한 많은 코드를 분리해내는지 당신에게 알려주는 또다른 예제가 되기를 바란다. WPF와 Silverlight의 강력한 데이터 바인딩 지원은 이 패턴을 아주 흥미롭게 만들어준다. 뷰와 뷰-모델 사이의 동기화 코드를 직접 작성하는 부분에 대한 걱정을 하지 않아도 되기 때문이다. 커맨드 패턴을 사용하면 어플리케이션의 액션을 UI 엘리먼트로부터 분리할 수 있다. 이로써 디자이너는 여러가지 액션을 호출하는 데 사용하는 UI 엘리먼트를 선택할 수 있는 자유를 얻는다. 그리고 무엇보다도, 우리는 Blend에서의 디자인 타임 지원을 그대로 유지하면서 이 모든 것을 해냈다. 다음 포스트에서는 의존성 주입(dependency injection)을 도입함으로써 YouCard 어플리케이션의 테스트 가능성을 높이는 작업을 계속할 것이다.


실컷 열심히 번역을 하고 보니 뒷북이란 걸 알게됐다. ㅜ.ㅜ 이미 실버라이트 카페의 boxmile 님께서 번역을 해두셨네. 다음에는 이런 삽질을 하지 말도록 하자.