MVVM и IOC: обработка инвариантов класса представления модели - PullRequest
29 голосов
/ 07 июня 2011

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

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

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

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

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

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

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

Edit:

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

Ответы [ 3 ]

7 голосов
/ 07 июня 2011

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

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

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

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

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

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

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

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

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

5 голосов
/ 07 июня 2011

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

  1. метод init проще реализовать, чем любые другие опции (ну, в Windsor's Typed Factory так же просто);
  2. слабость конструкции, связанная с отсутствием параметра contructor, может быть смягчена путем принудительной проверки параметров инициализации позднее в жизненном цикле виртуальной машины
  3. «место», где вы бы вызвали метод init, такое же, где вы бы назвали конструктор (или абстрактную фабрику);
  4. в отличие от абстрактной фабрики, вы можете выделить метод init в определенном интерфейсе для обработки нескольких виртуальных машин по разному пути наследования (если необходимо);
  5. это справедливый компромисс (по крайней мере, в этом контексте): если вы действительно не можете с этим смириться, просто зайдите на заводское решение, не заботясь о (очень небольших) сложностях.
2 голосов
/ 07 июня 2011

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

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

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

Итак, чтобы ответить, я бы использовал (1) и сломал«правило».

...