如何使用构造函数依赖注入将模型从集合中提供给它们的视图模型?

10

我在我的WPF应用程序中使用构造函数依赖注入,但经常遇到以下模式,因此希望听取他人的意见并了解其他解决方案。

目标是将一组ViewModel的层次结构与类似的Model层次结构相连接,以便每个模型的信息呈现责任都落在其自己的ViewModel实现上。(该模式也会在其他情况下出现,但MVVM应该是一个很好的例子。)

这里是一个简化的例子。假设我有一个包含进一步模型集合的模型:

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

public interface IAddress
{
}

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

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

public interface IAddressViewModel
{
}

子ViewModel需要呈现子Model的信息,因此通过构造函数进行注入:

public class AddressViewModel : IAddressViewModel
{
    private readonly IAddress _address;

    public AddressViewModel(IAddress address)
    {
        _address = address;
    }
}

问题是,如何最好地将子模型提供给相应的子视图模型?

这个例子很简单,但在典型的实际情况下,视图模型有更多的依赖项——每个依赖项都有自己的依赖项(依此类推)。我正在使用Unity 1.2(虽然我认为这个问题对其他IoC容器也是相关的),我正在使用Caliburn的视图策略来自动找到并连接适当的视图到视图模型。

这是我的当前解决方案:

父视图模型需要为每个子模型创建一个子视图模型,因此在初始化期间,它在构造函数中添加了一个工厂方法,供其使用:

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。该工厂方法使用一个子容器来注册 ViewModel 所需的 IAddress 依赖项,然后解析子 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

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

  • 这是使用Unity将子模型注入子ViewModel的最佳方法吗?
  • 是否有更好(或更快)的方式在其他IoC容器(例如Autofac)中执行此操作?
  • 如果给定MEF,该如何解决这个问题,考虑到它不是传统的IoC容器,但仍然用于组合对象?
2个回答

2
根据容器,您是否可以在工厂的CreateAddressViewModel方法中不指定参数(命名或其他)?
container.Resolve<IAddressViewModel>(new NamedParameterOverloads() { { "Address", model } };

根据容器的不同,您的工厂可能需要知道参数的名称(TinyIoC和Castle),或者它可能必须是构造函数依赖项列表中的最后一个(取决于容器)。虽然这不是很好,但它可以避免快速创建许多子容器和随之而来的GC抖动,并且您仍然可以为所有其他依赖项提供DI。
当然,如果您的VM还有一个需要相同IAddress的依赖项,那么除非您希望VM了解容器,否则子容器可能是正确的选择。
更新: 如果您正在使用使用“最后注册者获胜”的容器的子容器(我认为Unity就是这样做的),那么您每次都可以将相同的容器传递给工厂,并让您的工厂简单地注册新的IAddress-这样您就不会为每个迭代在堆上创建新的UnityContainer实例,并且如果您正在创建大量项目,应该可以减少垃圾收集。

正如您所指出的那样,如果任何依赖项需要该模型,则指定参数是行不通的,这对我来说是一个停滞不前的问题。然而,重复使用子容器是一种可能性。 - GraemeF

0

ViewModel这个样本应用程序展示了如何将Model和ViewModel结合起来,这个应用程序使用WPF Application Framework (WAF),并且使用MEF作为依赖注入框架。


浏览了一些示例,我没有找到任何地方做到这一点 - 它们倾向于创建一个单一的ViewModel,并在选择更改时将Model设置为它。也许我没有找对地方,你能指出你所想的那个类吗? - GraemeF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接