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

时间:2012-07-21 23:59:18

标签: mvvm constructor initialization viewmodel

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

可能的策略:

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

    在延迟初始化的情况下,需要知道ViewModel是否处于被认为可用的状态,IsBusy属性通常与其他异步和耗时的操作一样。这也意味着,因为ViewModel上的大多数属性都暴露从模型对象中检索的值,我们不得不编写以下类型的管道以确保模型可用。

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

    尽管检查很简单,但它只是增加了INPC和其他类型的必需品的管道,使ViewModel的编写和维护变得有些麻烦。在某些情况下,它变得更加成问题,因为可能不总是有一个合理的默认值从属性getter返回,这可能是布尔属性IsCommercialAccount的情况,如果没有可用的模型,返回它是没有意义的真或假会引起一系列其他设计问题,例如可空性。在上面的选项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));
    

    其他一些想法

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

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

1 个答案:

答案 0 :(得分:5)

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