Подтвердить что ты не робот

MVVM и IOC: обработка Просмотр инвариантов класса модели

Это проблема, с которой я сталкивался с тех пор, как начал использовать MVVM, сначала в WPF и теперь в Silverlight.

Я использую контейнер IOC для управления разрешением Views и ViewModels. Представления, как правило, очень простые, с конструктором по умолчанию, но ViewModels имеют тенденцию к доступу к реальным услугам, все из которых необходимы для их построения. Опять же, я использую контейнер МОК для разрешения, поэтому инъекционные услуги не являются проблемой.

Что становится проблемой - передача необходимых данных в ViewModel с помощью IOC. В качестве простого примера рассмотрим экран, который позволяет редактировать клиента. В дополнение к любым услугам, которые могут потребоваться, для ViewModel для этого экрана требуется объект клиента для отображения/редактирования данных клиента.

При разработке любого типа (не MVVM) библиотеки я считаю это непреложным правилом, что инварианты класса передаются через конструктор. В случаях, когда мне нужны контекстно-зависимые данные для времени построения класса, и рассматриваемый класс управляется контейнерами, я как правило использую абстрактный factory * в качестве моста. В MVVM это кажется излишним, так как большинству ViewModels потребуется их собственный factory.

Несколько других подходов, которые я попытался/рассмотрел, включили (1) метод инициализации/загрузки, в котором я передаю данные, что нарушает правило принудительного применения инвариантов класса через конструктор, (2) передачу данных через контейнер в качестве параметра переопределяет (Unity) и (3) передает данные через глобальную сумку состояния (тьфу).

Каковы альтернативные способы передачи данных, специфичных для контекста, из одного ViewModel в следующий? Может ли какая-либо из фреймворков MVVM решить эту конкретную проблему?

*, который может иметь свои собственные проблемы, например, требуя выбора между вызовом Container.Resolve() или отсутствием управления контейнером ViewModel. У Castle Windsor есть хорошее решение для этого, но AFAIK нет других рамок.

Edit:

Я забыл добавить: некоторые из перечисленных мной вариантов даже не возможны, если вы делаете MVVM "View First", если вы сначала не передаете данные в представление, а затем в ViewModel.

4b9b3361

Ответ 1

Раньше я много занимался этим вопросом. Насколько я могу судить, нет других жизнеспособных подходов; вы, кажется, уже глубоко задумались над этим вопросом. Я просто хочу, чтобы два добавили мой два 0,5 цента по причинам, почему я довольно часто выбираю вариант (1):

  • метод init проще реализовать, чем любые другие параметры (ну, Windsor Typed Factory так же просто);
  • слабость дизайна, не имеющая параметра contructor, может быть смягчена, обеспечив проверку параметров инициализации позже в жизненном цикле VM
  • "место", где вы вызываете метод init, будет таким же, как вы бы назвали конструктор (или абстрактный factory);
  • в отличие от абстрактного factory, вы можете разложить метод init в определенном интерфейсе, чтобы обрабатывать несколько виртуальных машин по разному пути наследования (если они были найдены);
  • Это справедливый компромисс (по крайней мере, в этом контексте): если вы действительно не можете с ним жить, просто перейдите к решению Factory, не заботясь о (очень мало) сложной накладной.

Ответ 2

Я не совсем понимаю, в чем проблема, поэтому я буду использовать простой и надуманный пример.

Скажем, у вас есть CustomerListViewModel, в котором содержится сводка каждого клиента. Когда вы выбираете клиента, вы хотите отобразить CustomerDetailViewModel. Это может привести либо идентификатор клиента, либо тип ICustomer, который ранее был заполнен в CustomerListViewModel с информацией о клиенте (в зависимости от того, когда вы хотите загрузить данные, например).

Я думаю, что вы спрашиваете, что произойдет, если CustomerDetailViewModel также принимает серию сервисов в качестве зависимостей, которые вы хотите разрешить через контейнер (обычно для цепей зависимостей).

Как вы сначала используете модель просмотра, вам нужно создать экземпляр CustomerDetailViewModel из CustomerListViewModel, и вы хотите сделать это через контейнер, чтобы соответствующие зависимости были введены соответствующим образом.

Поэтому, как вы упомянули, вы обычно делаете это с помощью абстрактного шаблона factory, например ICustomerDetailViewModelFactory, который передается как служба для CustomerListViewModel.

Этот тип factory имеет метод ICustomerDetailViewModel GetCustomerDetailViewModel(ICustomer customer). Этот тип factory потребует ссылки на ваш контейнер IoC.

При разрешении ICustomerDetailViewModel в вашем методе GetCustomerDetailViewModel factory вы можете указать значение, которое вы хотите использовать для параметра конструктора ICustomer, когда вы вызываете Resolve на свой контейнер.

Например, Unity имеет параметр переопределяет параметр и видит здесь для Castle Виндзорская поддержка. У Castle Windsor также есть набранный factory объект, так что вам не нужно реализовывать конкретные типы factory, просто абстракции.

Итак, я бы выбрал вариант 2! Мы используем Caliburn.Micro, он решает много проблем MVVM, но я не знаю каких-либо фреймворков, которые решают эту проблему.

Ответ 3

Я не уверен, что MVVM и IoC могут иметь инварианты классов в конструкторах. По моему опыту, ViewModels создаются в результате ICommand.Execute, что позволяет использовать простой двухэтапный процесс:

var vm = Container.Resolve<CustomerViewModel>();
vm.Model = CustomerRepository.GetCustomerModel(id);

На этом этапе мой просмотр не знает о ViewModel, и это произойдет только тогда, когда я вставляю ViewModel в любой контейнер, связанный с представлением. Я также использую DataTemplates для рендеринга ViewModel, что означает, что мне не нужно создавать экземпляр представления напрямую, чтобы предоставить DataContext.

Итак, чтобы ответить, я бы использовал (1) и нарушил "правило".