Как использовать инъекцию зависимости конструктора, чтобы поставлять модели из коллекции в свои ViewModels?

Я использую инъекцию зависимости конструктора в своем приложении WPF, и я продолжаю работать в следующем шаблоне, поэтому хотел бы получить мнение других людей и услышать об альтернативных решениях.

Цель состоит в том, чтобы связать иерархию ViewModels с аналогичной иерархией моделей, чтобы ответственность за представление информации в каждой модели была связана с ее собственной реализацией ViewModel. (Образец также появляется при других обстоятельствах, но MVVM должен сделать хороший пример.)

Вот упрощенный пример. Учитывая, что у меня есть модель, в которой есть коллекция следующих моделей:

public interface IPerson
{
    IEnumerable<IAddress> Addresses { get; }
}

public interface IAddress
{
}

Я хотел бы отразить эту иерархию в моделях ViewModels, чтобы я мог привязать ListBox (или что-то еще) к коллекции в Man 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 имеют больше зависимостей - каждый из которых имеет свои собственные зависимости (и т.д.). Я использую Unity 1.2 (хотя я думаю, что вопрос имеет отношение к другим контейнерам IoC), и я использую стратегии просмотра Caliburn для автоматического поиска и подключения соответствующего View к ViewModel.

Вот мое текущее решение:

Родительский ViewModel должен создать дочерний ViewModel для каждой дочерней модели, поэтому он добавляет метод factory к своему конструктору, который он использует во время инициализации:

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));
    }
}

A factory, который удовлетворяет интерфейсу Func<IAddress, IAddressViewModel>, регистрируется с основным UnityContainer. Метод factory использует дочерний контейнер для регистрации зависимости 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 очень ясны, и они легко проверяются и не знают о контейнере IoC. С другой стороны, производительность в порядке, но не велика, так как многие временные дочерние контейнеры могут быть созданы. Также я получаю много очень похожих методов factory.

  • Это лучший способ вставить дочерние модели в дочерние ViewModels с Unity?
  • Есть ли лучший (или более быстрый) способ сделать это в других контейнерах IoC, например. Autofac?
  • Как решить эту проблему с помощью MEF, учитывая, что он не является традиционным контейнером IoC, но все еще используется для компоновки объектов?

Ответ 1

В зависимости от контейнера вы не можете указать параметр (названный или иным образом) в вашем методе CreateAddressViewModel factory?

container.Resolve<IAddressViewModel>(new NamedParameterOverloads() { { "Address", model } };

В зависимости от контейнера ваш factory может знать имя параметра (TinyIoC и Castle afaik), или он, возможно, должен был быть последним в списке зависимостей конструктора (YMMV в зависимости от контейнеров), что не очень удобно, но это экономит время, чтобы создать множество дочерних контейнеров в быстром порядке, а GC thrashing будет следовать, и вы все равно получите DI для всех ваших других зависимостей.

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

Обновление: Если вы используете подконтейнер контейнера, который использует "последние победы в регистре" (что, я думаю, Unity делает), вы можете каждый раз передавать один и тот же дочерний контейнер в ваш factory, и ваш factory просто зарегистрирует новый IAddress - таким образом вы не будете создавать новый экземпляр UnityContainer в куче для каждой итерации, и он должен сократить коллекции мусора, если вы создаете множество элементов.

Ответ 2

Приложение примера ViewModel WPF Application Framework (WAF) показывает, как вы могли бы привести модель и ViewModel вместе. В образце используется MEF как Framework Injection Framework.