如何使用构造函数依赖注入将模型从集合提供给其ViewModel?

Gra*_*meF 10 dependency-injection mef inversion-of-control mvvm

我在我的WPF应用程序中使用构造函数依赖注入,并且我继续运行以下模式,因此希望得到其他人的意见并听取替代解决方案.

目标是将ViewModel的层次结构连接到类似的模型层次结构,以便在每个模型中呈现信息的责任在于其自己的ViewModel实现.(这种模式在其他情况下也会出现,但MVVM应该是一个很好的例子.)

这是一个简化的例子.鉴于我有一个模型,其中包含更多模型的集合:

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

public interface IAddress
{
}
Run Code Online (Sandbox Code Playgroud)

我想在ViewModel中镜像这个层次结构,以便我可以将ListBox(或其他)绑定到Person ViewModel中的集合:

public interface IPersonViewModel
{
    ObservableCollection<IAddressViewModel> Addresses { get; }
    void Initialize();
}

public interface IAddressViewModel
{
}
Run Code Online (Sandbox Code Playgroud)

子ViewModel需要显示来自子Model的信息,因此它是通过构造函数注入的:

public class AddressViewModel : IAddressViewModel
{
    private readonly IAddress _address;

    public AddressViewModel(IAddress address)
    {
        _address = address;
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是,将子Model提供给相应的子ViewModel的最佳方法是什么?

这个例子很简单,但在一个典型的实例中,ViewModels有更多的依赖 - 每个依赖都有自己的依赖(等等).我正在使用Unity 1.2(尽管我认为这个问题与其他IoC容器相关),我正在使用Caliburn的视图策略来自动查找并将相应的View连接到ViewModel.

这是我目前的解决方案:

父ViewModel需要为每个子Model创建一个子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));
    }
}
Run Code Online (Sandbox Code Playgroud)

满足Func<IAddress, IAddressViewModel>接口的工厂方法在main中注册UnityContainer.工厂方法使用子容器来注册IAddressViewModel所需的依赖项,然后解析子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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当PersonViewModel初始化时,它遍历Address模型中的每个并调用CreateAddressViewModel()(通过Func<IAddress, IAddressViewModel>参数注入).CreateAddressViewModel()创建一个临时子容器并注册IAddress模型,以便在解析IAddressViewModel子容器时,AddressViewModel通过其构造函数获取正确的实例.

这对我来说似乎是一个很好的解决方案,因为ViewModel的依赖关系非常清晰,并且它们很容易测试并且不知道IoC容器.另一方面,性能可以但不是很好,因为可以创建许多临时子容器.我最终得到了许多非常相似的工厂方法.

  • 这是将子模型注入带有Unity的子ViewModels的最佳方法吗?
  • 是否有更好(或更快)的方法在其他IoC容器中执行此操作,例如Autofac?
  • MEF如何处理这个问题,因为它不是传统的IoC容器,但仍然用于组合对象?

Ste*_*ins 2

根据容器,您是否可以不在工厂的 CreateAddressViewModel 方法中指定参数(命名或其他)?

container.Resolve<IAddressViewModel>(new NamedParameterOverloads() { { "Address", model } };
Run Code Online (Sandbox Code Playgroud)

根据容器的不同,您的工厂可能必须知道参数的名称(TinyIoC 和Castle afaik),或者它可能必须位于构造函数依赖项列表中的最后一个(YMMV 取决于容器),这不是很好,但它节省了快速连续创建大量子容器的时间,以及随之而来的 GC 抖动,并且您仍然可以为所有其他依赖项获得 DI。

当然,如果您的虚拟机还具有需要相同 IAddress 的依赖项,那么这种情况就会失败,在这种情况下,除非您希望虚拟机了解容器,否则可能需要使用子容器。

更新:如果您使用的容器的子容器使用“最后注册获胜”(我认为Unity是这样做的),那么您可以每次将相同的容器传递到您的工厂中,并让您的工厂简单地注册新的IAddress - 这样,您就不会为每次迭代在堆上创建一个新的 UnityContainer 实例,并且如果您创建大量项目,它应该会减少垃圾收集。