MVVM 및 서비스 패턴


14

MVVM 패턴을 사용하여 WPF 응용 프로그램을 작성 중입니다. 현재 뷰 모델은 서비스 계층을 호출하여 모델을 검색하고 (뷰 모델과 관련이없는 방법) 모델을 뷰 모델로 변환합니다. 생성자 주입을 사용하여 필요한 서비스를 viewmodel에 전달하고 있습니다.

쉽게 테스트 할 수 있고 의존성이 거의없는 뷰 모델에서 잘 작동하지만 복잡한 모델에 대해 viewModels를 만들려고하면 많은 서비스가 주입 된 생성자가 있습니다 (하나는 각 종속성과 사용 가능한 모든 값 목록을 검색합니다) 예를 들어 itemsSource에 바인딩합니다. 그런 여러 서비스를 처리하는 방법이 궁금하지만 단위 테스트를 쉽게 수행 할 수있는 뷰 모델이 있습니다.

몇 가지 해결책을 생각하고 있습니다.

  1. 사용 가능한 모든 서비스를 인터페이스로 포함하는 서비스 싱글 톤 (IServices) 작성. 예 : Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). 그렇게하면 많은 서비스 매개 변수가있는 거대한 생성자가 없습니다.

  2. viewModel에서 사용하는 서비스에 대한 파사드를 만들고이 객체를 내 viewmodel의 ctor에 전달합니다. 그러나 각 복잡한 뷰 모델 각각에 대한 파사드를 생성해야 할 것입니다.

이런 종류의 아키텍처를 구현하는 "올바른"방법은 무엇이라고 생각하십니까?


"올바른"방법은 서비스를 호출하고 ViewModel을 만드는 데 필요한 모든 캐스팅을 수행하는 별도의 계층을 만드는 것입니다. ViewModel은 자신을 생성 할 책임이 없습니다.
Amy Blankenship

@AmyBlankenship : 뷰 모델은 자신을 생성 할 필요는 없으며 (또는 필연적으로도 가능할 수는 없지만) 때로는 다른 뷰 모델 을 생성해야하는 책임이 있습니다 . 자동 공장 지원 기능이있는 IoC 컨테이너는 여기서 큰 도움이됩니다.
Aaronaught

"때때로 것"서로 다른 두 동물은 "해야한다";)
에이미 Blankenship의

@AmyBlankenship : 뷰 모델이 다른 뷰 모델을 생성하지 않아야한다고 제안하고 있습니까? 삼키기 힘든 약입니다. 뷰 모델이 new다른 뷰 모델을 생성하는 데 사용해서는 안된다는 것을 이해할 수 있지만 "새 문서"단추 나 메뉴를 클릭하면 새 탭이 추가되거나 새 창이 열리는 MDI 응용 프로그램처럼 단순한 것을 생각할 수 있습니다. 쉘 / 컨덕터 하나 또는 몇 개의 간접 레이어 뒤에 숨겨져 있어도 뭔가 새로운 인스턴스를 만들 수 있어야합니다 .
Aaronaught

글쎄, 분명히 어딘가에보기를 요청할 수있는 능력이 있어야합니다. 그러나 그것을 스스로 만들기 위해? 내 세상에는 없다 :). 그러나 다시, 내가 살고있는 세상에서 우리는 VM을 "프레젠테이션 모델"이라고 부릅니다.
Amy Blankenship

답변:


22

실제로,이 두 가지 솔루션 모두 나쁩니다.

사용 가능한 모든 서비스를 인터페이스로 포함하는 서비스 싱글 톤 (IServices) 작성. 예 : Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). 그렇게하면 많은 서비스 매개 변수가있는 거대한 생성자가 없습니다.

이것은 기본적으로 서비스 로케이터 패턴 이며 이는 안티 패턴입니다. 이렇게하면 개인 구현을 보지 않고 뷰 모델이 실제로 의존하는 것을 더 이상 이해할 수 없으므로 테스트 또는 리팩터링이 매우 어려워집니다.

viewModel에서 사용하는 서비스에 대한 파사드를 만들고이 객체를 내 viewmodel의 ctor에 전달합니다. 그러나 각 복잡한 뷰 모델 각각에 대한 파사드를 생성해야 할 것입니다.

이것은 안티 패턴이 아니지만 코드 냄새입니다. 본질적으로 매개 변수 객체를 만들고 있지만 PO 리팩토링 패턴의 요점은 자주 사용되는 많은 다른 위치에서 사용되는 매개 변수 세트를 처리하는 반면이 매개 변수는 한 번만 사용됩니다. 언급했듯이 실제 이점없이 많은 코드 팽창을 만들며 많은 IoC 컨테이너에서 잘 작동하지 않습니다.

실제로, 위의 두 전략은 전체 이슈를 간과하고 있는데, 이는 뷰 모델과 서비스 사이의 결합이 너무 높다는 것 입니다. 간단하게 숨어 서비스 로케이터에서 이러한 종속성 또는 파라미터 객체는 실제로하지 않는 변경 뷰 모델에 따라 얼마나 많은 다른 객체.

이러한 뷰 모델 중 하나를 어떻게 단위 테스트할지 생각해보십시오. 설치 코드는 얼마나 커질까요? 작동하려면 몇 가지를 초기화해야합니까?

MVVM으로 시작하는 많은 사람들이 전체 화면에 대한 뷰 모델을 만들려고 시도하는데 이는 근본적으로 잘못된 접근법입니다. MVVM은 구성 에 관한 것이며, 많은 기능을 가진 화면은 여러 가지 다른 뷰 모델로 구성되어야합니다. 각 뷰 모델은 하나 또는 몇 개의 내부 모델 / 서비스에만 의존합니다. 서로 통신해야하는 경우 pub / sub (메시지 브로커, 이벤트 버스 등)를 통해 통신합니다.

실제로해야 할 일은 의존성적은 뷰 모델을 리팩토링하는 것 입니다. 그런 다음 집계 "화면"이 필요한 경우 다른 뷰 모델을 작성하여 더 작은 뷰 모델을 집계합니다. 이 집계 뷰 모델은 그 자체로 많은 일을 할 필요가 없으므로 이해하고 테스트하기가 상당히 쉽습니다.

이 작업을 올바르게 수행했다면 코드를 살펴 보는 것만으로도 간단합니다. 간결하고 간결하며 구체적이며 테스트 가능한 뷰 모델이 있기 때문입니다.


그래, 그게 내가 끝낼거야! 고마워요
alfa-alfa

글쎄, 나는 그가 이미 시도했지만 성공하지 않았다고 가정했다. @ alfa-alfa
행복감

@Euphoric : 어떻게 "성공하지 못합니까?" Yoda가 말한 것처럼 :하지 말고 시도하지 마십시오.
Aaronaught

@Aaronaught 예를 들어 그는 실제로 단일 뷰 모델의 모든 데이터가 필요합니다. 어쩌면 그는 그리드를 가지고 있으며 다른 열은 다른 서비스에서 나옵니다. 작곡으로는 그렇게 할 수 없습니다.
Euphoric

@Euphoric : 사실, 당신은 할 수 조성물과 그 해결하지만 뷰 모델 수준 아래에 수행 할 수 있습니다. 올바른 추상화를 만드는 것입니다. 이 경우 ID 목록을 얻기 위해 초기 쿼리를 처리하고 자체 정보로 주석이 달린 "enrichers"의 시퀀스 / 목록 / 배열을 하나의 서비스 만 필요로합니다. 그리드 자체를 자체 뷰 모델로 만들고 효과적으로 두 가지 종속성으로 문제를 해결했으며 테스트하기가 매우 쉽습니다.
Aaronaught

1

나는 이것에 관한 책을 쓸 수 있었다 ... 사실 나는;)

첫째, 보편적으로 "올바른"방법 은 없습니다 . 다른 요소를 고려해야합니다.

서비스가 너무 세분화되었을 수 있습니다. 특정 Viewmodel 또는 관련 ViewModel 클러스터가 사용할 인터페이스를 제공하는 서비스를 Facade로 감싸는 것이 더 나은 솔루션 일 수 있습니다.

모든 뷰 모델이 사용하는 단일 Facade로 서비스를 래핑하는 것이 더 간단합니다. 물론 이것은 평균 시나리오에서 불필요한 기능이 많은 매우 큰 인터페이스 일 수 있습니다. 그러나 시스템의 모든 메시지를 처리하는 메시지 라우터와 다르지 않습니다.

사실, 많은 아키텍처가 결국에 발전한 것을 본 것은 이벤트 어 그리 게이터 패턴과 같은 것을 중심으로 구축 된 메시지 버스입니다. 테스트 클래스는 EA에 리스너를 등록하고 응답으로 적절한 이벤트를 발생시키기 때문에 테스트가 쉽습니다. 그러나 그것은 성장하는 데 시간이 걸리는 사전 시나리오입니다. 나는 통일 정면에서 시작하고 거기에서 가라고 말합니다.


대규모 서비스 외관은 메시지 브로커와 매우 다릅니다. 거의 종속성 스펙트럼의 반대편에 있습니다. 이 아키텍처의 특징은 발신자가 수신자에 대해 아무것도 모르고 많은 수신자 (pub / sub 또는 multicast)가있을 수 있다는 것입니다. 아마도 원격 프로토콜을 통해 기존 서비스를 노출시키는 RPC 스타일의 "원격"과 혼동하고 있고 발신자는 물리적 (종료점 주소)과 논리적 (반환 값) 모두 수신에 연결되어 있습니다.
Aaronaught

유사점은 파사드가 라우터처럼 작동한다는 것입니다. 발신자는 메시지를 보내는 클라이언트가 메시지를 처리하는 사람을 모르는 것처럼 호출을 처리하는 서비스 / 서비스를 모릅니다.
Michael Brown

그렇습니다. 그러나 정면은 신의 대상 입니다. 뷰 모델이 갖는 모든 종속성을 가지고 있습니다. 아마도 여러 모델이 공유하기 때문일 것입니다. 실제로, 당신은 처음에 그렇게 열심히 일했던 느슨한 결합의 이점을 제거했습니다. 이제는 무언가가 mega-façade에 닿을 때마다 그것이 실제로 어떤 기능에 의존하는지 알 수 없기 때문입니다. Façade를 사용하는 수업을위한 단위 테스트 작성. 정면의 모형을 만듭니다. 자, 어떤 방법을 조롱합니까? 설치 코드는 어떻게 생겼습니까?
Aaronaught

브로커는 또한 메시지 처리기의 구현에 대해 아무것도 모르기 때문에 이것은 메시지 브로커와 매우 다릅니다 . 후드 아래에서 IoC를 사용합니다. Façade는 수신자에게 전화를 전달해야하기 때문에 수신자에 대한 모든 정보를 알고 있습니다. 버스에는 제로 커플 링이 있습니다. 외관은 외설적으로 높은 원심 결합을 가지고 있습니다. 어디에서나 변경하는 거의 모든 것이 파사드에도 영향을 미칩니다.
Aaronaught

나는 혼란의 일부를 여기에서 생각합니다. 그리고 이것이 상당히 많이 보았습니다 . 그것은 의존성이 의미하는 것입니다. 다른 클래스에 의존하는 클래스가 있지만 해당 클래스의 4 가지 메소드를 호출하는 경우 1이 아닌 4 개의 종속성이 있습니다. 모든 클래스를 Façade 뒤에 놓아도 종속성 수는 변경되지 않으므로 이해하기가 더 어려워집니다. .
Aaronaught

0

왜 둘 다 결합하지 않습니까?

파사드를 작성하고 뷰 모델이 사용하는 모든 서비스를 배치하십시오. 그러면 잘못된 S 단어없이 모든 뷰뷰 모델에 대해 단일 파사드를 가질 수 있습니다.

또는 생성자 주입 대신 속성 주입을 사용할 수 있습니다. 그러나 그런 다음 제대로 주입되는지 확인해야합니다.


pseudo-C #으로 예제를 제공하면 더 나은 답변입니다.
Robert Harvey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.