MVVM: ViewModel和业务逻辑的连接

69

在使用MVVM模式完成了几个项目之后,我仍然困惑于ViewModel的角色:

我过去做过的事情: 1. 仅将Model用作数据容器。 2. 将操作数据的逻辑放到ViewModel中。(这是业务逻辑吧?) 缺点:逻辑不可重用。

现在我尝试的是: 尽可能使ViewModel变得简单。 将所有逻辑移动到Model层。 仅在ViewModel中保留表示逻辑。 缺点:如果数据在Model层内被更改,则UI通知会变得非常麻烦。

下面我会举个例子来让它更加清晰:

场景: 重命名文件的工具。 类: File:表示每个文件; Rule:包含如何重命名文件的逻辑;

如果我遵循第一种方法: 为File、Rule和View创建一个RenamerViewModel的ViewModel。 将所有逻辑都放在RenamerViewModel中: 包括FileViewModel和RuleViewModel的列表以及处理逻辑。 简单快捷,但不可重用。

如果我遵循第二种方法: 创建一个新的模型类-Renamer,其中包含一个File、Rule列表和迭代每个文件并应用每个规则的处理逻辑。 为File、Rule和Renamer创建一个ViewModel。 现在,RenamerViewModel仅包含Renamer模型的一个实例,以及两个ObservableCollections,用于包装Renamer的File和Rule列表。 但是,所有逻辑都在Renamer Model中。因此,如果通过方法调用触发Renamer Model来操纵某些数据,则ViewModel不知道操纵哪些数据。 因为Model不包含任何PropertyChange通知,而我会避免这种情况。 因此,业务逻辑和表示逻辑被分开,但这使得通知UI变得更加困难。

5个回答

72
将业务逻辑放在视图模型中是非常糟糕的做法,所以我会快速地说一下永远不要这样做,然后转而讨论第二个选择。
将逻辑放在模型中要合理得多,这是一个很好的起点。有什么缺点吗?你的问题说:
“因此,如果Renamer Model通过方法调用触发操作某些数据,则ViewModel不知道操作哪些数据。因为模型不包含任何PropertyChange通知,我将避免这种情况。”
好吧,让你的模型实现INotifyPropertyChanged肯定可以让你继续更好的事情。但是,有时可能无法这样做——例如,该模型可能是部分类,其中属性由工具自动生成且不会引发更改通知。这很不幸,但并非世界末日。
如果您想购买某物,那么某人必须支付;如果不是模型提供这些通知,那么你只剩下两个选择:
  1. 视图模型知道对模型上的哪些操作(可能)会导致更改,并在每个这样的操作之后更新其状态。
  2. 其他人知道哪些操作会导致更改,他们会在模型更改后通知视图模型更新其状态。
第一种选择再次是一个糟糕的想法,因为实际上它是回归到将“业务逻辑”放在视图模型中。虽然不像将所有业务逻辑都放在视图模型中那么糟糕,但仍然不好。
第二个选项更有前途(也更需要实现):
  • 将您的部分业务逻辑放在单独的类中(称为“服务”)。服务将通过适当地使用模型实例来实现您希望执行的所有业务操作。
  • 这意味着服务知道模型属性何时可能会更改(这是可以的:模型+服务==业务逻辑)。
  • 该服务将向所有感兴趣的方提供更改的模型通知; 您的viewmodels将依赖于该服务并接收这些通知(因此它们将知道“它们”的模型何时被更新)。
  • 由于业务操作也由服务实现,因此这仍然非常自然(例如,当在viewmodel上调用命令时,反应是在服务上调用适当的方法;请记住,viewmodel本身不知道业务逻辑)。
  • 有关此类实现的更多信息,请参见我的答案这里这里


    2
    通知事项通常被描述为ViewModel部分,这就是为什么我会避免在Model中使用它。这感觉像是做了两次同样的事情。 - JDeuker
    1
    @J.D.:当然可以,但要么这样做,要么实现服务。由你决定。 - Jon
    @Jon:对于“永远不要那样做”,我点赞。习惯于N层模型的开发人员往往会忘记,将WPF库引用添加到VM中是可以的,可以返回需要组合的复杂对象,例如FlowDocument。 - Mike Christian
    1
    +1 一些很好的想法。如果您有一些可观察的状态,您认为这个可观察的状态应该存储在服务中,因为它是由服务更新的吗?在这种情况下,ViewModel只需“公开”此可观察对象以供视图控制器观察,对吗? - Mitch Dart

    14

    两种方法都是有效的,但还有第三种方法:在模型层和VM层之间实现一个服务。如果您想保持模型的简单性,服务可以提供一个与UI无关的中间人,以可重用的方式强制执行您的业务规则。

    因为模型不包含任何PropertyChange通知,我将避免使用这个

    为什么要避免这样做?别误会,我倾向于尽可能让我的模型尽可能简单,但在您的模型中实现更改通知有时可能很有用,并且只有在这样做时才依赖于System.ComponentModel。它完全与UI无关。


    服务使用模型还是视图模型实例? - JDeuker
    1
    @J.D.: 模型 - 服务层不依赖于视图层。您可以将服务层视为增强和保护模型层的完整性。 - Kent Boogaart
    我发现避免在模型中使用INotifyPropertyChanged的原因是,它会暗示可以将其绑定到UI上,但有时即使是处理适量数据的批处理也会导致性能不佳,因为UI正在响应PropertyChanged,但由于数据量太大而无法更新自身,从而严重拖慢应用程序。在这种情况下,最好等到数据加载完成后再更新UI。 - Piotr Golacki

    5

    我做以下事情:

    1. XAML视图逻辑

    2. ViewModel处理点击事件和创建新的ViewModel。处理路由事件等。

    3. Model是我的数据容器,包含验证模型数据的业务逻辑。

    4. Services用于填充模型数据。例如从Web服务器调用、从磁盘加载、保存到磁盘等。根据示例,通常我的Model和Service都将实现IPropertyChanged接口,或者它们可能具有事件处理程序。

    按照我个人的看法,任何复杂的应用程序都需要添加另一层。我把它称为model+service、view、viewmodel。Service抽象了您的业务逻辑,并以模型实例作为依赖项或创建模型。


    视图模型如何处理点击事件?点击事件由视图处理。 - user1034912
    1
    命令处理程序本身应该在视图模型中,而视图通过ICommand与之绑定。 - rollsch

    3
    似乎很多MVVM开发者对业务逻辑的定义存在误解。如所说,服务(我称之为应用逻辑)+模型(业务实体或DTO)=业务逻辑。任何使用可用数据进行计算的代码都是业务逻辑的一部分。
    例如,下面的网站明确表示“税费计算逻辑”应该放在ViewModel中,这是完全错误的,也是由于对业务逻辑定义的混淆造成的。 enter image description here 来源:https://www.clariontech.com/blog/wpf-with-mvvm-easily-separate-ui-and-business-logic 其中一个原因可能是当他们谷歌“业务逻辑”时,第一个结果可能是由数据库管理员提供的定义。显然,[Database.]Business_Logic和[MVVM.]Business_Logic是两个完全不同含义的单词。
    不幸的是,尽管MVVM的目的很清晰,依赖层之间的原因也已经解释了,但这种混淆正在变得越来越普遍。

    -1
    你也可以在Model和ViewModel上实现IDataErrorInfo,但只在Model中进行验证,这将使您在仅在Model实现业务规则时更加轻松...

    例如:

    ViewModel:

    ...
    
    private Person person;
    
    ...
    
    string IDataErrorInfo.this[string propertyName]
    {
        get
        {
            string error = (person as IDataErrorInfo)[propertyName];
            return error;
        }
    }
    

    模型:

    public class Person:INotifyPropertyChanged,IDataErrorInfo
    {
    
    ...
    
       string IDataErrorInfo.this[string propertyName]
       {
            get { return this.GetValidationError(propertyName); }
       }
    
    ...
    
       string GetValidationError(string propertyName)
       {
            if(propertyName == "PersonName")
                 //do the validation here returning the string error
       }
    }
    

    此外,可以看一下MVCVM模式,我正在使用它,将业务逻辑抽象到控制器类中而不是模型或视图模型中,效果非常好。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接