【问题标题】:MVVM: ViewModel and Business Logic ConnectionMVVM:ViewModel 和业务逻辑连接
【发布时间】:2013-04-26 15:42:21
【问题描述】:

在使用 MVVM 模式做了几个项目后,我仍然在为 ViewModel 的角色苦苦挣扎:

我过去做了什么: 仅将模型用作数据容器。 将逻辑用于操作 ViewModel 中的数据。 (这就是业务逻辑对吗?) 缺点:逻辑不可重用。

我现在正在尝试什么: 保持 ViewModel 尽可能薄。 将所有逻辑移入模型层。 仅在 ViewModel 中保留演示逻辑。 缺点:如果模型层内的数据发生更改,UI 通知会非常痛苦。

所以我给你举个例子让大家更清楚:

场景: 重命名文件的工具。 课程: File :代表每个文件; 规则:包含如何重命名文件的逻辑;

如果我遵循方法 1: 为文件、规则和视图创建 ViewModel -> RenamerViewModel。 将所有逻辑放在 RenamerViewModel 中: 包含 FileViewModel 和 RuleViewModel 的列表以及进行的逻辑。 简单快捷,但不可重复使用。

如果我遵循方法 2: 创建一个新的模型类 -> 重命名器,其中包含一个文件列表、规则和后续逻辑,以对每个文件进行交互并应用每个规则。 为文件、规则和重命名器创建视图模型。 现在 RenamerViewModel 只包含一个 Renamer Model 的实例,外加两个 ObservableCollections 来包装 Renamer 的 File und Rule List。 但整个逻辑都在重命名模型中。因此,如果 Renamer Model 被触发以通过方法调用来操作某些数据,则 ViewModel 不知道操作了哪些数据。 因为模型不包含任何 PropertyChange 通知,我会避免这种情况。 所以业务逻辑和表现逻辑是分开的,但是这使得通知 UI 变得很困难。

【问题讨论】:

    标签: c# wpf design-patterns mvvm


    【解决方案1】:

    将业务逻辑放在视图模型中是一种非常糟糕的做事方式,所以我要快速说永远不要这样做,然后继续讨论第二个选项。

    将逻辑放入模型中更加合理,这是一个很好的开始方法。有什么缺点?你的问题说

    因此,如果触发 Renamer Model 以通过 Method 操作某些数据 调用,ViewModel 没有线索,哪些数据被操纵。因为 模型不包含任何 PropertyChange 通知,我会 避免这种情况。

    好吧,让你的模型实现 INotifyPropertyChanged 肯定会让你继续做更好的事情。但是,确实有时无法做到这一点——例如,模型可能是一个部分类,其中属性由工具自动生成并且不会引发更改通知。这很不幸,但不是世界末日。

    如果你想买东西,那么有人必须付钱;如果不是提供此类通知的模型,那么您只有两个选择:

    1. viewmodel 知道模型上的哪些操作(可能)导致更改,并在每次此类操作后更新其状态。
    2. 其他人知道哪些操作会导致更改,并通知视图模型在其包装的模型发生更改后更新其状态。

    第一个选项又是一个坏主意,因为实际上它又回到了将“业务逻辑”放入视图模型中。不如将所有业务逻辑放在视图模型中那么糟糕,但仍然如此。

    第二个选项更有希望(不幸的是需要更多的工作来实现):

    • 将部分业务逻辑放在单独的类(“服务”)中。该服务将通过适当地使用模型实例来实现您想要执行的所有业务操作。
    • 这意味着服务知道模型属性何时可能更改(这没关系:模型 + 服务 == 业务逻辑)。
    • 该服务将向所有相关方提供有关更改模型的通知;您的视图模型将依赖于服务并接收这些通知(因此他们会知道“他们的”模型何时更新)。
    • 由于业务操作也由服务实现,这很自然(例如,当在视图模型上调用命令时,反应是调用服务上的适当方法;请记住,视图模型本身并不了解业务逻辑)。

    有关此类实现的更多信息,另请参阅我的回答 herehere

    【讨论】:

    • 通知的东西总是被描述为 ViewModel 部分,这就是为什么我会在模型中避免它。感觉就像做两次一样的事情。
    • @J.D.:当然,但要么就是这样,要么就是实现服务。您的来电。
    • @Jon:“永远不要那样做”+1。习惯于 N 层模型的开发人员往往会忘记向 VM 添加 WPF 库引用是可以的,以交回需要组合的复杂对象,例如 FlowDocument。
    • +1 一些好主意。如果您有一些可观察的状态,您是否认为可观察的状态应该存在于服务中,因为它是由服务更新的?在这种情况下,ViewModel 只会“暴露”这个 observable 以便视图控制器正确地观察它?
    【解决方案2】:

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

    因为模型不包含任何 PropertyChange 通知,我会避免这种情况

    您为什么要避免这种情况?不要误会我的意思,我倾向于让我的模型尽可能地笨拙,但是在你的模型中实现更改通知有时会很有用,当你这样做时你只依赖System.ComponentModel。它完全与 UI 无关。

    【讨论】:

    • 服务是否使用模型或视图模型实例?
    • @J.D.: 模型 - 服务层不依赖视图层。您可以将服务层视为增强和保护模型层的完整性。
    【解决方案3】:

    我做以下事情

    1. 仅使用 XAML 视图逻辑查看

    2. 处理点击处理程序和创建新视图模型的视图模型。处理路由事件等。

    3. 模型是我的数据容器和关于验证模型数据的业务逻辑。

    4. 用数据填充模型的服务。例如调用网络服务器,从磁盘加载,保存到磁盘等。根据示例,我的模型和服务通常都会实现 IPropertyChanged。或者他们可能有事件处理程序。

    任何复杂的应用程序 imo 都需要另一层。我称之为模型+服务、视图、视图模型。该服务抽象您的业务逻辑并将模型实例作为依赖项或创建模型。

    【讨论】:

    • 视图模型如何处理点击?点击由视图处理
    • 命令处理程序本身应该在视图模型中,并且视图通过 ICommand 绑定到它
    【解决方案4】:

    您还可以在 Model 和 ViewModel 上实现 IDataErrorInfo,但只在 Model 中进行验证,这将简化您仅在 Model 上实现业务规则的方式...

    例如:

    视图模型:

    ...
    
    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 模式,我实际使用它,将业务逻辑抽象为控制器类而不是模型或视图模型非常好

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-24
      • 2012-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-07
      相关资源
      最近更新 更多