是否应通过构造函数,属性或方法调用初始化ViewModel

jpi*_*son 11 constructor initialization mvvm viewmodel

我在MVVM的上下文中讨论了一些不同的设计概念,主要源于何时初始化ViewModel的问题.在"初始化"方面更具体,我指的是加载值,例如选择值,安全上下文以及在某些情况下可能导致几秒延迟的其他事情.

可能的策略:

  1. 将参数传递给ViewModel构造函数并在构造函数中加载.
  2. 仅支持ViewModel上的无参数构造函数,而是支持初始化接受参数并进行加载的方法.
  3. 选项1和2的组合,其中参数传递给ViewModel构造函数,但加载延迟直到调用Initialize方法.
  4. 选项3的变体,而不是传递给ViewModel构造函数的参数,它们直接在属性上设置.

影响ViewModel属性的getter和setter

在初始化被推迟的情况下有必要知道视图模型是否为其IsBusy财产通常作用就像它对于其他异步和耗时的操作被认为是可用的状态.这也意味着,因为ViewModel上的大多数属性都暴露从模型对象中检索的值,我们不得不编写以下类型的管道以确保模型可用.

public string Name
{
    get 
    {  
        if (_customerModel == null) // Check model availability
        {
            return string.Empty;
        }

        _customerModel.Name;
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管检查很简单,但它只是增加了INPC的管道和其他类型的必需品,使ViewModel的编写和维护变得有些麻烦.在某些情况下,它变得更加成问题,因为可能并不总是有一个合理的默认值从属性getter返回,这可能是布尔属性IsCommercialAccount的情况,如果没有可用的模型,返回true或者返回true是没有意义的错误地引起了一系列其他设计问题的质疑,例如可空性.在上面的选项1的情况下,我们将所有内容传递给构造函数并加载它,然后我们只需要关注View中的NULL ViewModel,并且当ViewModel不为null时,保证初始化.

支持延迟初始化

使用选项4,还可以依赖于可以在ViewModel的基类中实现的ISupportInitialize,以提供一致的方式来发信号通知ViewModel是否已初始化,并且还通过标准方法BeginInit启动初始化.这也可以在选项2和3的情况下使用,但如果所有初始化参数都在一个原子事务中设置,则没有意义.至少以这种方式,上面显示的条件可能变成类似的东西

设计如何影响IoC

就IoC而言,我理解选项1和3可以使用通常优选的构造函数注入来完成,并且选项2和4可以分别使用方法和属性注入来完成.然而,我关注的不是IoC或如何传递这些参数,而是整体设计以及它如何影响ViewModel实现及其公共接口,尽管我希望成为一个好公民,如果有必要,可以使IoC更容易未来.

可测性

这三个选项似乎支持可测性同样的概念,没有太大的帮助在这些选项之间做出决定,虽然这是值得商榷的选项4可能需要一个更广泛的测试,以确保性能的正常行为如该行为依赖于初始化状态.

指挥能力

选项2,3和4都有副作用,要求View中的代码隐藏在ViewModel上调用初始化方法,但是如果需要,这些可以作为命令公开.在大多数情况下,可能会在构造之后直接加载调用这些方法,如下所示.

var viewModel = new MyViewModel();
this.DataContext = viewModel;
// Wrap in an async call if necessary
Task.Factory.StartNew(() => viewModel.InitializeWithAccountNumber(accountNumber));
Run Code Online (Sandbox Code Playgroud)

其他一些想法

我已经尝试过这些策略的变体,因为我一直在使用MVVM设计模式,但尚未就最佳实践做出总结.我很想听听社区的想法,并尝试就初始化ViewModel或在其处于不可用状态时处理其属性的最佳方式找到合理的共识.

理想情况可能是使用状态模式,其中ViewModel本身与表示不同状态的不同ViewModel对象交换.因此,我们可以有一个通用的BusyViewModel实现,它表示忙状态,它消除了ViewModel上IsBusy属性的一个需求,然后当下一个ViewModel准备就绪时,它在View上被换出,允许ViewModel遵循概述的状态在选项1中,它在施工期间完全初始化.这留下了一些关于谁负责管理状态转换的问题,例如,BusyViewModel可以负责抽象类似于BackgroundWorker或正在进行初始化的Task,并在准备好时呈现内部ViewModel.关于另一方面视图交换DataContext的可能需要或者处理在搜索的事件或给人以查看到BusyViewModel所以它可以在传统的状态模式的意义上被设置的DataContext属性的有限访问.如果人们在这些方面做了类似的事情,我肯定想知道,因为我的谷歌搜索还没有发现.

Col*_*inE 5

面向对象设计的一般方法,无论是创建视图模型还是其他类型的类,都是这样的; 可以传递给构造函数的所有东西都应该传递给构造函数.这减少了具有某种IsInitialized状态的需要,并使您的对象不那么复杂.有时某些框架使得很难遵循这种方法,例如IoC容器(尽管它们应该允许构造函数注入),但我仍然坚持它作为一般规则.

  • 好答案.我还要补充说,在使用对象之前需要的任何东西都应该在构造函数中初始化. (2认同)
  • 我的一般方法是给构造函数提供它真正需要的依赖关系但是从不做I/O,处理,逻辑构造函数中的任何操作!示例:如果缺少配置,则对象创建不应失败.当DB不可用时,它不应该失败.由于稳定性,可测试性,复杂性,可维护性等原因,不应在构造函数中加载数据... (2认同)