C#中MVVM模式的介绍

我们来详细介绍一下 C# 中的 MVVM 模式。这是一个在现代 WPF、Xamarin.Forms、Uno Platform 甚至 ASP.NET Blazor 中都非常重要和流行的架构模式。

1. 什么是 MVVM 模式?

MVVM 是 Model-View-ViewModel 的缩写,它是一种专门为构建数据绑定友好的应用程序而设计的软件架构模式。其核心思想是 “关注点分离”,将用户界面逻辑与业务逻辑分离开来,使代码更易于维护、测试和理解。

MVVM 源自于经典的 MVC 和 MVP 模式,但通过数据绑定机制,极大地减少了 View 和 ViewModel 之间的“胶水代码”。

2. MVVM 的三个核心组件

让我们通过一个简单的比喻来理解:
想象一个餐厅:

  • View: 餐厅的装潢、菜单、桌椅。顾客能看到和交互的一切。
  • ViewModel: 服务员。他接收顾客的指令,去后厨下单,然后把做好的菜端上来。他本身不做菜,但他是顾客和后厨之间的桥梁。
  • Model: 后厨和厨师。他们负责准备真实的食材和烹饪,不直接与顾客打交道。

a. Model (模型)

  • 职责: 代表实际的业务逻辑和数据。它包含了应用程序的核心功能和数据。
  • 例子
    • 从数据库或网络服务获取数据的类。
    • 代表业务实体的类,如 Customer, Product, Order
    • 数据访问层。
  • 关键点: Model 完全不知道 View 和 ViewModel 的存在。它只关心如何存储、检索和操作数据。

b. View (视图)

  • 职责: 定义用户在屏幕上看到的结构和外观。它负责 UI 的呈现和用户输入的捕获。
  • 例子: 在 WPF 中是一个 .xaml 文件及其对应的 .xaml.cs 代码隐藏文件。
  • 关键点
    • 理想情况下,View 的“代码隐藏”文件应该几乎为空,不包含任何业务逻辑。
    • View 通过 数据绑定 与 ViewModel 进行通信。
    • 它通过触发 命令 或利用 行为 来响应用户交互(如按钮点击)。
    • View 知道 ViewModel,但 ViewModel 不知道 View。

c. ViewModel (视图模型)

  • 职责: 它是 View 的抽象。它包含 View 所需的数据和命令,并将 Model 转换成 View 可以轻松绑定的格式。它是连接 Model 和 View 的桥梁。
  • 关键点
    • 它包含属性: 这些属性通常是公开的,并支持属性更改通知(例如,通过实现 INotifyPropertyChanged 接口)。当 ViewModel 中的属性值改变时,View 会自动更新。
    • 它包含命令: 这些是封装了操作的方法,可以被 View 中的控件(如按钮)绑定。通常通过实现 ICommand 接口来实现(例如 DelegateCommandRelayCommand)。
    • 它不知道 View: ViewModel 不应该持有对任何 UI 控件(如 ButtonTextBox)的引用。

3. MVVM 是如何工作的?数据流

MVVM 的魔力在于数据绑定命令绑定

  1. 数据流向 View (Model -> ViewModel -> View)
    • ViewModel 从 Model 中获取数据(例如,一个 List<Product>)。
    • ViewModel 将这些数据暴露为易于绑定的属性(例如,一个 ObservableCollection<ProductViewModel>)。
    • View 的 UI 控件(如 ListView)通过数据绑定到 ViewModel 的属性。当 ViewModel 中的数据发生变化时,UI 会自动更新。
  2. 用户交互流向业务逻辑 (View -> ViewModel -> Model)
    • 用户在 View 上执行一个操作,例如点击一个“保存”按钮。
    • 该按钮绑定到 ViewModel 中的一个 ICommand(例如 SaveCommand)。
    • ViewModel 的 SaveCommand 被执行。
    • SaveCommand 的内部逻辑会调用 Model 层的方法来持久化数据。

4. 实现 MVVM 的关键技术

a. INotifyPropertyChanged (INPC)

这是一个至关重要的接口,它允许 ViewModel 在属性值改变时通知 View。没有它,数据绑定就只能是单向的(从 ViewModel 到 View 的初始设置)。

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // CallerMemberName 特性自动获取调用此方法的属性名
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    // 辅助方法,用于设置属性值并触发通知
    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

public class MainViewModel : ViewModelBase
{
    private string _name;
    public string Name
    {
        get => _name;
        set => SetField(ref _name, value); // 使用 SetField 方法
    }
}
b. ICommand

它允许将 UI 事件(如按钮点击)绑定到 ViewModel 中的方法。

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

在 ViewModel 中的使用:

public class MainViewModel : ViewModelBase
{
    public RelayCommand SaveCommand { get; }

    public MainViewModel()
    {
        SaveCommand = new RelayCommand(
            execute: _ => SaveData(), // 点击时执行的方法
            canExecute: _ => !string.IsNullOrEmpty(Name) // 决定按钮是否可用的方法
        );
    }

    private void SaveData()
    {
        // 调用 Model 层保存数据
    }
}

在 XAML 中的绑定:

<Button Content="Save" Command="{Binding SaveCommand}"/>

5. MVVM 的优势

  1. 分离关注点: UI 设计人员可以专注于 XAML 设计,而开发人员可以专注于 ViewModel 和 Model 的逻辑。
  2. 可测试性: ViewModel 不依赖于 View,因此可以在没有 UI 的情况下进行单元测试。你可以轻松地测试业务逻辑和命令。
  3. 可维护性: 由于代码组织良好,修改 UI 或底层逻辑变得更容易,且不易产生副作用。
  4. 代码复用: 同一个 ViewModel 可以被不同的 View 使用(例如,为手机和桌面平台提供不同的 View,但共享同一个 ViewModel)。

6. 流行的 MVVM 框架

虽然你可以完全手动实现 MVVM,但使用成熟的框架可以节省大量时间,它们提供了强大的基础功能(如依赖注入、事件聚合器、导航等)。

  • CommunityToolkit.Mvvm: 轻量级、高性能、模块化,是现在的官方推荐。
  • Prism: 功能非常丰富,适合大型企业级应用。
  • MVVM Light: 一个轻量级框架,历史悠久,但现在更推荐 CommunityToolkit.Mvvm。
  • Caliburn.Micro: 强调“约定优于配置”。

总结

MVVM 是构建复杂、可维护和可测试的 XAML 应用程序的黄金标准。它通过数据绑定将 View 和 ViewModel 解耦,使得开发流程更加清晰和高效。虽然初期学习曲线稍陡,但其带来的长期收益是巨大的。对于任何认真的 C# XAML 开发者来说,掌握 MVVM 都是一项必备技能。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐