如果构造函数不应该做任何工作,那么如何在 MVVM 中初始化 Views 和 ViewModels

If constructors should do no work, how about initialization of Views and ViewModels in MVVM

有很多关于 OO 最佳实践的书籍推荐不做实际工作的构造函数。也就是说,您应该有一个 "clean" 构造函数,以便您始终可以在单元测试中实例化一个新对象,例如,应该在其他地方进行额外的工作(通常在某些 initialize() 方法等).具体来说,new 关键字在构造函数中的出现有些令人不悦。

但是,在 WPF/MVVM 中,情况并非总是如此。这个成语很常见,例如:

public SomeView : Window
{
    InitializeComponent();
    this.DataContext = new SomeViewModel();  // or the XAML equivalent
}

我一直在创建应用程序,希望在启动应用程序时一切都 "assemble itself",但我想知道它是否以某种方式违反了 "no work in constructor" 原则,and/or 我是否应该采取一些预防措施关于它。

(到目前为止,最烦人的事情(确实很常见)是在 XAML 中实例化 ViewModel 时出现 XamlParseException 并引发一些错误。)

您没有 unit test 观看次数,所以我认为设置 DataContext 不会成为问题或违反任何最佳实践。

而不是 "newing"View 的构造函数中的 ViewModel,我个人更喜欢在 XAML 中设置它。您可能会考虑从 IoC 容器中获取 ViewModel。例如

using Microsoft.Practices.ServiceLocation;
using GalaSoft.MvvmLight.Ioc;

public class ViewModelLocator
{
    static ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        SimpleIoc.Default.Register<RepositoryBase<Model>, ModelRepository>();
        SimpleIoc.Default.Register<MainViewModel>();
    }

    public MainViewModel MainVM
    {
        get { return ServiceLocator.Current.GetInstance<MainViewModel>(); }
    }
    ...
}

<phone:PhoneApplicationPage
    ...
    x:Class="Namespace.MainPage"
    DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}"
</phone:PhoneApplicationPage>

例如,对于 ViewModels,注入依赖项的方法实际上只有几种。那将是构造函数或 属性 注入。我也不认为那算作 "doing work",所以只需重载默认构造函数,不用担心 "dem rules" :)

考虑以下 ViewModel。是 "doing work" 吗?

public class MainViewModel 
{
    // if feeling lazy, provide default dependencies
    public MainViewModel()
        :this(new ModelRepository()) 
    {}

    // attribute for IoC/dependency injection
    [PreferredConstructor]
    public MainViewModel(RepositoryBase<Model> modelRepository)
    {
        ModelRepository = modelRepository;
        Task.Factory.StartNew(() => Initialize());
    }

    public RepositoryBase<Model> ModelRepository { get; set;}

    private async Task Initialize()
    {
        // Do stuff; populate properties for binding etc.
        var models = await ModelRepository.GetAllAsync();
        ...
    }
}

我认为不是。

如果 IoC 设置正确,它有默认构造函数和重载,当 运行 应用程序时可以正常工作,而且它也很容易测试。在单元测试中,只需在构造函数中传入 mock 存储库(或通过 属性,但构造函数更方便)。

例如

[TestClass]
public class MainViewModelTests
{
    [TestMethod]
    public void SomeTest()
    {
        var models = new ... // collection
        var repository = new Mock<RepositoryBase<Model>>();
        repository.Setup(r => r.GetAllAsync()).Returns(Task.FromResult(models));

        var viewModel = new MainViewModel(repository.Object); 

        // Assert stuff...
    }
}

So you are implying that "new viewmodel in construction" is something that should be avoided if possible, is that it? What about attaching some initialization to the Loaded event?

我没有暗示什么。我个人更喜欢使用 XAML,因为这会使代码隐藏最初变得干净。

如果可以避免的话,我不太喜欢使用 View 的事件。如果事件绑定到 ViewModel 中的 Command 则没问题。

我通常不会等待 Loaded 事件等,而是会在 ViewModel 构造完成后立即对其进行初始化;从存储库获取数据,填充 ObservableCollections View 将绑定到,等等...参见上面的示例。

我首先等待 View 加载并且仅在初始化它的 DataContext 后会出现什么情况(无法避免)?