런타임시 뷰 모델 작성을 덜 고통스럽게 만드는 방법


17

나는 긴 질문에 대해 사과하고, 그것은 rant로 조금 읽지 만, 그렇지 않다고 약속합니다! 아래에 내 질문을 요약했습니다.

MVC 세계에서는 상황이 간단합니다. 모델에는 상태가 있고,보기 에는 모델이 표시 되며, 컨트롤러 모델에 대한 작업을 수행하며 (기본적으로) 컨트롤러에는 상태가 없습니다. 작업을 수행 하기 위해 Controller는 웹 서비스, 저장소, 로트에 대한 종속성이 있습니다. 컨트롤러를 인스턴스화 할 때 이러한 종속성을 제공하는 것에 관심이 있습니다. 작업을 실행할 때 (컨트롤러의 방법) 해당 종속성을 사용하여 모델을 검색 또는 업데이트하거나 다른 도메인 서비스를 호출합니다. 어떤 사용자가 특정 항목의 세부 정보를 보려는 것처럼 컨텍스트가 있으면 해당 항목의 Id를 매개 변수로 Action에 전달합니다. Controller의 어느 곳에도 상태에 대한 참조가 없습니다. 여태까지는 그런대로 잘됐다.

MVVM을 입력하십시오. 나는 WPF를 좋아하고 데이터 바인딩을 좋아합니다. Caliburn Micro atm을 사용하여 ViewModels에 데이터를 더 쉽게 바인딩 할 수있는 프레임 워크를 좋아합니다. 나는이 세상에서 일이 덜 간단하다고 생각합니다. 하자 다시 운동을 할 : 모델 상태보기가 보여 뷰 모델을, 그리고 뷰 모델이 수행 모델 (기본적으로)와 /에 물건을하는 뷰 모델은 수행 상태를 가지고! 위해 (하나 개 이상의 모델에 아마 위임 모든 속성을,하지만 수단 그 자체의 상태를있는 모델 중 하나의 방법으로 또는 다른에 대한 참조가 있어야 명확하게) ViewModel은 웹 서비스, 저장소, 로트에 의존합니다. ViewModel을 인스턴스화 할 때 해당 종속성뿐만 아니라 상태도 제공하는 것이 중요합니다. 그리고 신사 숙녀 여러분, 나는 끝까지 귀찮게합니다.

당신은 인스턴스를 필요로 할 때마다 ProductDetailsViewModel으로부터 ProductSearchViewModel(있는 당신이 호출 된 ProductSearchWebService차례로 돌려 IEnumerable<ProductDTO>, 당신이이 가지 중 하나를 수행 할 수 있습니다? 나와 함께 아직도, 모두) :

  • call new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);, 이것은 나쁘다, 3 개의 의존성을 더 상상해라. 이것은 의존성 ProductSearchViewModel을 가져야 한다는 것을 의미한다 . 또한 생성자를 변경하는 것은 고통 스럽습니다.
  • call _myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);, 팩토리는 단지 Func이며, 대부분의 IoC 프레임 워크에 의해 쉽게 생성됩니다. Init 메소드가 누출되는 추상화이기 때문에 이것이 나쁘다고 생각합니다. 또한 Init 메소드에 설정된 필드에는 readonly 키워드를 사용할 수 없습니다. 몇 가지 이유가 더있을 것입니다.
  • call _myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);so ... 이것은 일반적으로 이러한 유형의 문제에 권장되는 패턴 (추상 공장)입니다. 나는 실제로 사용하기 시작할 때까지 정적 타이핑에 대한 갈망을 충족시키기 때문에 천재적이었습니다. 상용구 코드의 양은 너무 많이 생각합니다 (사용하는 어리석은 변수 이름과는 별도로). 런타임 매개 변수가 필요한 각 ViewModel에 대해 두 개의 추가 파일 (공장 인터페이스 및 구현)이 제공되며 4 개의 추가 시간과 같은 비 런타임 종속성을 입력해야합니다. 그리고 종속성이 변경 될 때마다 팩토리에서도 변경됩니다. 더 이상 DI 컨테이너를 사용하지 않는 것 같습니다. ( Castle Windsor에는 이것에 대한 일종의 해결책이 있다고 생각 합니다.
  • 익명의 유형이나 사전으로 무언가를하십시오. 나는 정적 타이핑을 좋아합니다.

네 이러한 방식으로 상태와 동작을 혼합하면 MVC에는 전혀 존재하지 않는 문제가 발생합니다. 그리고 현재이 문제에 대한 적절한 해결책이 없다고 생각합니다. 이제 몇 가지 사항을 관찰하고 싶습니다.

  • 사람들은 실제로 MVVM을 사용합니다. 그래서 그들은 위의 모든 것을 신경 쓰지 않거나 다른 훌륭한 해결책을 가지고 있습니다.
  • WPF가있는 MVVM에 대한 심층적 인 예를 찾지 못했습니다. 예를 들어, NDDD 샘플 프로젝트는 일부 DDD 개념을 이해하는 데 크게 도움이되었습니다. 누군가가 MVVM / WPF와 비슷한 방향으로 나를 가리킬 수 있다면 정말로 좋아합니다.
  • 어쩌면 MVVM을 모두 잘못하고있을 것이므로 디자인을 뒤집어 놓아야합니다. 어쩌면 나는이 문제가 전혀 없어야합니다. 글쎄, 나는 다른 사람들이 같은 질문을 했으므로 내가 유일한 사람은 아니라고 생각합니다.

요약

  • ViewModel을 상태와 동작의 통합 지점으로 만드는 것이 MVVM 패턴 전체에 어려움을 겪는 이유라고 결론을 내릴 수 있습니까?
  • 추상 팩토리 패턴을 사용하는 것이 ViewModel을 정적으로 유형화하는 유일한 방법 / 최상의 방법입니까?
  • 심도있는 참조 구현과 같은 것이 있습니까?
  • 디자인 냄새가 많은 상태 / 동작을 가진 많은 ViewModel이 있습니까?

10
읽기에 너무 길어서 수정을 고려하십시오. 관련성이없는 항목이 많이 있습니다. 사람들이 모든 것을 읽지 않아도되기 때문에 좋은 대답을 놓칠 수 있습니다.
yannis

Caliburn.Micro를 좋아한다고 말했지만이 프레임 워크가 새로운 뷰 모델을 인스턴스화하는 데 어떻게 도움이되는지 모르십니까? 그것의 몇 가지 예를 확인하십시오.
Euphoric

@Euphoric 좀 더 구체적으로 말씀해 주시면 Google에서 도와 드리지 않는 것 같습니다. 검색 할 수있는 키워드가 있습니까?
dvdvorle

3
MVC를 약간 단순화하고 있다고 생각합니다. 보기에는 처음에 모델이 표시되지만 작동 중에는 상태가 변경됩니다. 이 변화하는 상태는 제 생각에 "모델 편집"입니다. 즉, 일관성 제한이 감소 된 평평한 버전의 모델입니다. 실제로 편집 모델이라고하는 것은 MVVM ViewModel입니다. 이전에 MVC에서 View에 의해 유지되었거나 커밋되지 않은 Model의 커밋되지 않은 버전으로 되돌려 놓은 상태입니다. 그래서 당신은 전에 "유속"상태를 가졌습니다. 이제 모든 것이 ViewModel에 있습니다.
Scott Whitlock

@ ScottWhitlock MVC를 실제로 단순화하고 있습니다. 그러나 "유속"상태가 ViewModel에 있다고 잘못 말하는 것은 아닙니다. 거기에서 동작을 크 래밍하면 ViewModel을 다른 ViewModel에서 사용 가능한 상태로 초기화하기가 더 어려워집니다. MVC의 "모델 편집"은 자체 저장 방법을 모릅니다 (저장 방법이 없음). 그러나 컨트롤러는이를 알고 있으며이를 수행하는 데 필요한 모든 종속성이 있습니다.
dvdvorle

답변:


2

새 뷰 모델을 시작할 때의 종속성 문제는 IOC로 처리 할 수 ​​있습니다.

public class MyCustomViewModel{
  private readonly IShoppingCartWebService _cartService;

  private readonly ITimeService _timeService;

  public ProductDTO ProductDTO { get; set; }

  public ProductDetailsViewModel(IShoppingCartWebService cartService, ITimeService timeService){
    _cartService = cartService;
    _timeService = timeService;
  }
}

컨테이너를 설정할 때 ...

Container.Register<IShoppingCartWebService,ShoppingCartWebSerivce>().As.Singleton();
Container.Register<ITimeService,TimeService>().As.Singleton();
Container.Register<ProductDetailsViewModel>();

뷰 모델이 필요한 경우 :

var viewmodel = Container.Resolve<ProductDetailsViewModel>();
viewmodel.ProductDTO = myProductDTO;

캘리 번 마이크로 와 같은 프레임 워크를 사용할 때 종종 일부 형태의 IOC 컨테이너가 이미 존재합니다.

SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);

1

나는 매일 ASP.NET MVC와 함께 일하고 1 년 이상 WPF에서 일해 왔으며 이것이 내가 보는 방법입니다.

MVC

컨트롤러는 작업을 조정해야합니다 (이것을 가져 와서 추가하십시오).

뷰는 모델을 표시합니다.

이 모델은 일반적으로 데이터 (예 : UserId, FirstName)와 상태 (예 : 제목)를 포함하며 일반적으로보기에 따라 다릅니다.

MVVM

모델은 일반적으로 데이터 (예 : UserId, FirstName) 만 보유하며 일반적으로 전달됩니다.

뷰 모델에는 뷰의 동작 (메서드), 해당 데이터 (모델) 및 상호 작용 (명령)이 포함됩니다. 발표자가 모델을 인식하는 활성 MVP 패턴과 유사합니다. 뷰 모델은 뷰에 따라 다릅니다 (1 뷰 = 1 뷰 모델).

뷰는 뷰 모델에 대한 데이터 및 데이터 바인딩을 표시합니다. 뷰가 생성되면 일반적으로 관련 뷰 모델이 뷰와 함께 생성됩니다.


기억해야 할 것은 MVVM 프리젠 테이션 패턴은 데이터 바인딩 특성으로 인해 WPF / Silverlight에만 해당됩니다.

뷰는 일반적으로 어떤 뷰 모델과 관련이 있는지 (또는 하나의 추상화)를 알고 있습니다.

뷰 모델이 뷰마다 인스턴스화되어 있어도 뷰 모델을 싱글 톤으로 처리하는 것이 좋습니다. 다시 말해, IOC 컨테이너를 통해 DI를 통해이를 생성하고 적절한 메소드를 호출 할 수 있어야합니다. 매개 변수를 기반으로 모델을로드합니다. 이 같은:

public partial class EditUserView
{
    public EditUserView(IContainer container, int userId) : this() {
        var viewModel = container.Resolve<EditUserViewModel>();
        viewModel.LoadModel(userId);
        DataContext = viewModel;
    }
}

예를 들어, 업데이트중인 사용자 별 뷰 모델을 만들지 않고 대신 모델에 뷰 모델에서 일부 호출을 통해로드되는 사용자 별 데이터가 포함됩니다.


내 이름이 "Peter"이고 내 제목이 { "Rev", "Dr"} * 인 경우 왜 FirstName 데이터와 제목 상태를 고려합니까? 아니면 당신의 예를 분명히 할 수 있습니까? * 실제로는 아님
Pete Kirkham

@PeteKirkham-콤보 박스와 관련하여 언급 한 '제목'예제. 일반적으로 유지할 정보를 보낼 때 선택하는 데 사용 된 상태 (예 : 주 /도 / 제목 목록)를 보내지 않습니다. 데이터와 함께 전송할 가치가있는 상태 (예 : 사용중인 사용자 이름)는 상태가 오래되었을 수 있으므로 (메시지 큐잉과 같은 일부 비동기 패턴을 사용하는 경우) 처리 시점에서 확인해야합니다.
Shelakel

이 게시물 이후 2 년이 지났지 만 미래의 시청자에게 유리한 의견을 제시해야합니다. View는 하나의 ViewModel에 해당 할 수 있지만 ViewModel은 여러 개의 Views로 표시 될 수 있습니다. 둘째, Service Locator 안티 패턴에 대해 설명합니다. IMHO 당신은 어디에서나 뷰 모델을 직접 해결해서는 안됩니다. 그것이 DI의 목적입니다. 가능한 적은 지점에서 해결하십시오. 예를 들어 Caliburn이이 작업을 수행하도록하십시오.
Jony Adamit

1

질문에 대한 짧은 답변 :

  1. 예 State + Behavior는 이러한 문제로 이어지지 만 모든 OO에 해당됩니다. 실제 범인은 일종의 SRP 위반 인 ViewModel의 결합입니다.
  2. 아마도 정적으로 입력했을 것입니다. 그러나 다른 ViewModel에서 ViewModel을 인스턴스화해야 할 필요성을 줄이거 나 제거해야합니다.
  3. 내가 알지 못한다.
  4. 아니요, 그러나 관련이없는 상태 및 동작을 가진 ViewModel이있는 경우 (일부 모델 참조 및 일부 ViewModel 참조와 유사)

긴 버전 :

우리는 같은 문제에 직면하여 도움이 될만한 것들을 발견했습니다. 비록 "마법"해결책을 모르지만, 그러한 것들이 고통을 약간 완화시키고 있습니다.

  1. 변경 내용 추적 및 유효성 검사를 위해 DTO에서 바인딩 가능한 모델을 구현하십시오. 이러한 "데이터"-ViewModel은 서비스에 의존해서는 안되며 컨테이너에서 제공되지 않아야합니다. 그것들은 단지 "새로"만들어 질 수 있고, DTO에서 파생 될 수도 있습니다. 결론은 애플리케이션 (MVC와 같은)에 특정한 모델을 구현하는 것입니다.

  2. ViewModel을 분리하십시오. Caliburn을 사용하면 ViewModel을 쉽게 연결할 수 있습니다. 심지어 화면 / 도체 모델을 통해 제안합니다. 그러나이 결합은 ViewModel을 단위 테스트하기 어렵고 많은 종속성을 생성하며 가장 중요합니다. ViewModel에서 ViewModel 수명주기를 관리해야하는 부담을 가중시킵니다. 그것들을 분리하는 한 가지 방법은 네비게이션 서비스 또는 ViewModel 컨트롤러와 같은 것을 사용하는 것입니다. 예 :

    공용 인터페이스 IShowViewModels {void Show (object inlineArgumentsAsAnonymousType, string regionId); }

어떤 형태의 메시징으로이를 수행하는 것이 더 좋습니다. 그러나 중요한 것은 다른 ViewModel에서 ViewModel 수명주기를 처리하지 않는 것입니다. MVC 컨트롤러에서 서로 의존하지 않으며 MVVM ViewModel에서 서로 의존해서는 안됩니다. 다른 방법으로 통합하십시오.

  1. 컨테이너를 "문자열"형식 / 동적 기능을 사용하십시오. INeedData<T1,T2,...>유형 안전 작성 매개 변수와 같은 것을 작성 하고 적용 하는 것이 가능할 수도 있지만 그만한 가치는 없습니다. 또한 각 ViewModel 유형에 대한 팩토리를 작성하는 것은 가치가 없습니다. 대부분의 IoC 컨테이너는 이에 대한 솔루션을 제공합니다. 런타임에 오류가 발생하지만 디커플링 및 단위 테스트 가능성은 그만한 가치가 있습니다. 여전히 어떤 종류의 통합 테스트를 수행하고 있으며 이러한 오류를 쉽게 발견 할 수 있습니다.

0

일반적으로 PRISM을 사용 하여이 작업을 수행하는 방식은 각 어셈블리에 컨테이너 초기화 모듈이 포함되어 있으며 모든 인터페이스, 인스턴스가 시작시 등록됩니다.

private void RegisterResources()
{
    Container.RegisterType<IDataService, DataService>();
    Container.RegisterType<IProductSearchViewModel, ProductSearchViewModel>();
    Container.RegisterType<IProductDetailsViewModel, ProductDetailsViewModel>();
}

그리고 예제 클래스가 주어지면 컨테이너가 완전히 통과하면서 이와 같이 구현됩니다. 이렇게하면 이미 컨테이너에 액세스 할 수 있으므로 새로운 종속성을 쉽게 추가 할 수 있습니다.

/// <summary>
/// IDataService Interface
/// </summary>
public interface IDataService
{
    DataTable GetSomeData();
}

public class DataService : IDataService
{
    public DataTable GetSomeData()
    {
        MessageBox.Show("This is a call to the GetSomeData() method.");

        var someData = new DataTable("SomeData");
        return someData;
    }
}

public interface IProductSearchViewModel
{
}

public class ProductSearchViewModel : IProductSearchViewModel
{
    private readonly IUnityContainer _container;

    /// <summary>
    /// This will get resolved if it's been added to the container.
    /// Or alternately you could use constructor resolution. 
    /// </summary>
    [Dependency]
    public IDataService DataService { get; set; }

    public ProductSearchViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void SearchAndDisplay()
    {
        DataTable results = DataService.GetSomeData();

        var detailsViewModel = _container.Resolve<IProductDetailsViewModel>();
        detailsViewModel.DisplaySomeDataInView(results);

        // Create the view, usually resolve using region manager etc.
        var detailsView = new DetailsView() { DataContext = detailsViewModel };
    }
}

public interface IProductDetailsViewModel
{
    void DisplaySomeDataInView(DataTable dataTable);
}

public class ProductDetailsViewModel : IProductDetailsViewModel
{
    private readonly IUnityContainer _container;

    public ProductDetailsViewModel(IUnityContainer container)
    {
        _container = container;
    }

    public void DisplaySomeDataInView(DataTable dataTable)
    {
    }
}

컨테이너에 대한 참조를 포함하는 모든 뷰 모델에서 파생 된 ViewModelBase 클래스를 사용하는 것이 일반적입니다. 모든 뷰 모델을 해결하는 습관을 new()'ing들이는 한 모든 의존성 해결을 훨씬 간단하게 만들어야합니다.


0

때때로 그것의 좋은 오히려 본격적인 예보다 간단한 정의로 이동합니다 : http://en.wikipedia.org/wiki/Model_View_ViewModel은 어쩌면 ZK 자바 예제를 읽고 더 많은 C #을 하나보다 조명한다.

다른 경우에는 직감에 귀를 기울이십시오.

디자인 냄새가 많은 상태 / 동작을 가진 많은 ViewModel이 있습니까?

모델이 테이블 당 오브젝트입니까? ORM은 비즈니스를 처리하거나 여러 테이블을 업데이트하는 동안 도메인 개체에 매핑하는 데 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.