mvvm - 如何使用构造函数依赖注入(inject)将模型从集合提供给他们的 ViewModel?

标签 mvvm dependency-injection inversion-of-control mef

我在我的 WPF 应用程序中使用构造函数依赖注入(inject),并且一直遇到以下模式,因此希望获得其他人的意见并了解替代解决方案。

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

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

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

public interface IAddress
{
}

我想在 ViewModels 中镜像这个层次结构,以便我可以将 ListBox(或其他)绑定(bind)到 Person ViewModel 中的集合:
public interface IPersonViewModel
{
    ObservableCollection<IAddressViewModel> Addresses { get; }
    void Initialize();
}

public interface IAddressViewModel
{
}

子 ViewModel 需要呈现来自子 Model 的信息,所以它是通过构造函数注入(inject)的:
public class AddressViewModel : IAddressViewModel
{
    private readonly IAddress _address;

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

问题是,将子模型提供给相应的子 ViewModel 的最佳方式是什么?

这个例子很简单,但在典型的真实案例中,ViewModel 有更多的依赖关系——每个都有自己的依赖关系(等等)。我正在使用 Unity 1.2(尽管我认为这个问题与其他 IoC 容器相关),并且我正在使用 Caliburn 的 View 策略来自动查找适当的 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));
    }
}

满足 Func<IAddress, IAddressViewModel> 的工厂方法接口(interface)注册到主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> 参数注入(inject))。 CreateAddressViewModel()创建一个临时子容器并注册 IAddress模型,以便当它解析 IAddressViewModel从子容器 AddressViewModel获取通过其构造函数注入(inject)的正确实例。

这对我来说似乎是一个很好的解决方案,因为 ViewModel 的依赖关系非常清晰,它们很容易测试并且不知道 IoC 容器。另一方面,性能还可以,但不是很好,因为可以创建很多临时子容器。此外,我最终得到了很多非常相似的工厂方法。
  • 这是使用 Unity 将子模型注入(inject)子 ViewModel 的最佳方法吗?
  • 在其他 IoC 容器中是否有更好(或更快)的方法,例如自动法?
  • 鉴于 MEF 不是传统的 IoC 容器但仍用于组合对象,如何使用 MEF 解决这个问题?
  • 最佳答案

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

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

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

    当然,如果您的 VM 还具有需要相同 IAddress 的依赖项,则这种情况会下降,在这种情况下,除非您希望 VM 了解容器,否则子容器可能是可行的方法。

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

    关于mvvm - 如何使用构造函数依赖注入(inject)将模型从集合提供给他们的 ViewModel?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2803376/

    相关文章:

    具有依赖关系的子 ViewModel 的 WPF/MVVM 导航

    dependency-injection - StructureMap MVC 5 html.Action 问题

    swift - 带有 RxSwift 的 MVVM-C : '[weak self]' in closure

    c# - MVVM 和 WPF 结构

    java - Spring 中的共享原型(prototype) bean

    c# - 如何在 C# 中使用带有继承的依赖注入(inject)

    c# - 如何使用 .Net IO 类创建可测试代码?

    c# - WPF 将应用程序命令绑定(bind)到 ViewModel ICommand

    silverlight - 绑定(bind)样式名称并更改样式 VIA View Model

    javascript - 为什么Angular.element常用而不需要注入(inject)?