Я использую внедрение зависимости конструктора в своем приложении WPF, и я продолжаю сталкиваться со следующим шаблоном, так хотел бы получить мнение других людей о нем и услышать об альтернативных решениях.
Цель состоит в том, чтобы обеспечить электричеством иерархию ViewModels к подобной иерархии Моделей, так, чтобы ответственность за представление информации в каждой модели лежала на своей собственной реализации ViewModel. (Шаблон также неожиданно возникает при других обстоятельствах, но MVVM должен сделать для хорошего примера.)
Вот упрощенный пример. Учитывая, что у меня есть модель, которая имеет набор дальнейших моделей:
public interface IPerson
{
IEnumerable<IAddress> Addresses { get; }
}
public interface IAddress
{
}
Я хотел бы зеркально отразить эту иерархию в ViewModels так, чтобы я мог связать ListBox (или безотносительно) к набору в Человеке ViewModel:
public interface IPersonViewModel
{
ObservableCollection<IAddressViewModel> Addresses { get; }
void Initialize();
}
public interface IAddressViewModel
{
}
Дочерний ViewModel должен представить информацию из дочерней Модели, таким образом, он введен через конструктора:
public class AddressViewModel : IAddressViewModel
{
private readonly IAddress _address;
public AddressViewModel(IAddress address)
{
_address = address;
}
}
Вопрос, что лучший способ состоит в том, чтобы предоставить дочернюю Модель к соответствующему дочернему ViewModel?
Пример тривиален, но в типичном реальном случае ViewModels имеют больше зависимостей - каждый из которых имеет свои собственные зависимости (и так далее). Я использую Единицу 1.2 (хотя я думаю, что вопрос является актуальным через другие контейнеры МОК), и я использую стратегии представления Меча автоматически найти и обеспечить электричеством соответствующее Представление к ViewModel.
Вот мое текущее решение:
Родительский ViewModel должен создать дочерний ViewModel для каждой дочерней Модели, таким образом, ему добавили метод фабрики к его конструктору, которого он использует во время инициализации:
public class PersonViewModel : IPersonViewModel
{
private readonly Func<IAddress, IAddressViewModel> _addressViewModelFactory;
private readonly IPerson _person;
public PersonViewModel(IPerson person,
Func<IAddress, IAddressViewModel> addressViewModelFactory)
{
_addressViewModelFactory = addressViewModelFactory;
_person = person;
Addresses = new ObservableCollection<IAddressViewModel>();
}
public ObservableCollection<IAddressViewModel> Addresses { get; private set; }
public void Initialize()
{
foreach (IAddress address in _person.Addresses)
Addresses.Add(_addressViewModelFactory(address));
}
}
Метод фабрики, который удовлетворяет Func<IAddress, IAddressViewModel>
интерфейс регистрируется в основном UnityContainer
. Метод фабрики использует дочерний контейнер для регистрации IAddress
зависимость, которая требуется ViewModel и затем разрешает дочерний ViewModel:
public class Factory
{
private readonly IUnityContainer _container;
public Factory(IUnityContainer container)
{
_container = container;
}
public void RegisterStuff()
{
_container.RegisterInstance<Func<IAddress, IAddressViewModel>>(CreateAddressViewModel);
}
private IAddressViewModel CreateAddressViewModel(IAddress model)
{
IUnityContainer childContainer = _container.CreateChildContainer();
childContainer.RegisterInstance(model);
return childContainer.Resolve<IAddressViewModel>();
}
}
Теперь, когда PersonViewModel
инициализируется, это циклично выполняется через каждого Address
в Модели и вызовах CreateAddressViewModel()
(который был введен через Func<IAddress, IAddressViewModel>
аргумент). CreateAddressViewModel()
создает временный дочерний контейнер и регистрируется IAddress
модель так, чтобы, когда это решает IAddressViewModel
от дочернего контейнера AddressViewModel
ввели корректный экземпляр через его конструктора.
Это, кажется, хорошее решение меня, поскольку зависимости ViewModels очень ясны, и они являются легко тестируемыми и не знающий о контейнере МОК. С другой стороны, производительность в порядке, но не большая, поскольку много временных дочерних контейнеров может быть создано. Также я заканчиваю с большим количеством очень похожих методов фабрики.
В зависимости от контейнера, можете ли вы не указать параметр (названный или иначе) в методе CreateAddressViewModel вашей фабрики?
container.Resolve<IAddressViewModel>(new NamedParameterOverloads() { { "Address", model } };
В зависимости от контейнера вашей фабрике может потребоваться знать имя параметра (TinyIoC и Castle afaik), или он должен был быть последним в списке зависимостей конструктора (YMMV в зависимости от контейнеров), что не очень хорошо, но экономит создание большого количества дочерних контейнеров в быстрой последовательности, и сбор мусора, который будет следовать, и вы по-прежнему получаете DI для всех других ваших зависимостей.
Конечно, это не так, если ваша виртуальная машина также имеет зависимость, требующую того же IAddress, в этом случае дочерний контейнер, вероятно, будет подходящим вариантом, если вы не хотите, чтобы виртуальная машина знала о контейнере.
Обновление: Если вы используете подконтейнер контейнера, который использует "выигрыш последнего регистра" (что, я думаю, использует Unity), то вы можете передать тот же дочерний контейнер в ваш Factory каждый раз, и пусть ваша фабрика просто регистрирует новый IAddress - таким образом вы не будете создавать новый экземпляр UnityContainer в куче для каждой итерации, и он должен сократить сборку мусора, если вы создаете много элементов.
Пример приложения ViewModel WPF Application Framework (WAF) показывает, как можно объединить модель и ViewModel. В примере используется MEF в качестве инфраструктуры внедрения зависимостей.