11

我在 MVVM 的上下文中与一些不同的设计概念作斗争,这些概念主要源于何时初始化 ViewModel 的问题。更具体地说,在“初始化”方面,我指的是加载值,例如选择值、安全上下文以及在某些情况下可能导致几秒钟延迟的其他内容。

可能的策略:

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

对 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 的情况,如果没有可用的模型,则返回 true 或false 对一系列其他设计问题(例如可空性)提出质疑。在上面的选项 1 的情况下,我们将所有内容传递给构造函数并加载它,然后我们只需要关注 View 中的 NULL ViewModel,并且当 ViewModel 不为 null 时,它保证被初始化。

支持延迟初始化

使用选项 4,还可以依赖ISupportInitialize,它可以在 ViewModel 的基类中实现,以提供一致的方式来指示 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 的 DataContext 属性的有限访问权限授予 BusyViewModel,以便可以在传统的状态模式意义上进行设置。如果人们在这些方面做类似的事情,我肯定想知道,因为我的谷歌搜索还没有出现太多。

4

1 回答 1

5

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

于 2012-07-23T05:50:52.687 回答