MVC и MVVM — знакомые, похожие, но разные

Если Вы работаете с C#/.NET – наверняка Вы слышали, пробовали и использовали оба архитектурных паттерна. Но, не будем лукавить, многим практикующим программистам часто не нравится абстрактный язык дизайна. Мы просто берём за основу шаблоны, сделанные коллегами, авторами книг или AI, и добавляем туда новые методы, новые переменные, новые свойства.

Но давайте также согласимся, что иногда весьма полезно уйти на высоту абстрактной модели и внимательно посмотреть на тот уже трудно распознаваемый начальный рисунок проекта.

Это нужно, чтобы понять суть базовых концепций и, часто, постараться «отрефакторить» Ваше создание: говоря на литературном языке остальной части человечества — постараться сделать его более совершенным.

Итак, два архитектурных паттерна, используемых для похожих и разных типов проектов:

Model-View-Controller и Model-View-ViewModel

Оба содержат Model и View.

Model (модель) — там, где лежат данные. Либо, в более сложных вариантах, это слой, разделенный на подслои, но не будем пока усложнять.

Модель содержит классы, реализующие данные и методы, манипулирующие данными непосредственно. Например, EntityFramework. Или Drapper. Или просто классы с прямыми вызовами SqlClient. Либо — просто классы, хранящие данные в памяти.

View – визуализация. То, что видит пользователь. Обычно довольно глупая, но красивая сущность.

Есть немного разницы, а именно:

Model-View-Controller

View отображает данные от Controller. Обычно не делает ничего само, а просто получает данные от Controller (о нём ниже).

Например:

@model List<Product>

<ul>
@foreach (var p in Model)
{
    <li>@p.Name - $@p.Price</li>
}
</ul>

показывает, сколько стоит каждый продукт.

Model-View-ViewModel

View связана с ViewModel, причем двухсторонне, используя Data Binding. То есть действия пользователя передаются ниже, в View Model, а ViewModel может обновлять элементы View.

Например:

<Entry Placeholder="Name" Text="{Binding NewName}" />

отображает данные от ViewModel, а

<Button Text="Add" Command="{Binding AddCommand}" />

передает данные от действия пользователя во ViewModel.

И вот теперь о главных «действующих» сущностях: Controller и ViewModel.

Model-View-Controller

Controller – получает ввод пользователя, управлялет моделью и подготавливает данные для View. Потом вызывает полное (view.Render(model)) или частичное (view.UpdateSection(data
)) обновление View.

Пример:

MVC: ручное частичное обновление, контроллер вызывает обновление метки.

// ===== Model =====
class CounterModel {
    public int Value { get; private set; }
    public void Increment() => Value++;
}

// ===== View =====
class CounterView {
    private Label _label;
    private Button _button;

    public event Action IncrementClicked;

    public CounterView(Label label, Button button) {
        _label = label;
        _button = button;
        _button.Click += (s, e) =>   IncrementClicked?.Invoke();
    }

    public void UpdateLabel(int value) {
        _label.Text = $"Count: {value}";
    }
}

// ===== Controller =====
class CounterController {
    private CounterModel _model;
    private CounterView _view;

    public CounterController(CounterModel model, CounterView view) {
        _model = model;
        _view = view;
        _view.IncrementClicked += OnIncrement;
        _view.UpdateLabel(_model.Value); // начальное отображение
    }

    private void OnIncrement() {
        _model.Increment();
        _view.UpdateLabel(_model.Value); // частичное обновление View
    }
}
Model-View-ViewModel

ViewModel — управляет состоянием представления (то есть UI), содержит его данные, передает данные в модель, реализует команды. Каждое изменение в ViewModel сразу видно в View.

Пример:

MVVM: ViewModel только меняет данные, а View сама реагирует через привязки.

// ===== ViewModel =====
class CounterViewModel : INotifyPropertyChanged {
    private int _value;
    public int Value {
        get => _value;
        set {
            if (_value != value) {
                _value = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
            }
        }
    }

    public ICommand IncrementCommand { get; }

    public CounterViewModel() {
        IncrementCommand = new Command(() => Value++);
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

// ===== View (XAML, например MAUI/WPF) =====
/*
<VerticalStackLayout>
    <Label Text="{Binding Value}" />
    <Button Text="Increment" Command="{Binding IncrementCommand}" />
</VerticalStackLayout>
*/

// ===== Code-behind =====
public partial class CounterPage : ContentPage {
    public CounterPage() {
        InitializeComponent();
        BindingContext = new CounterViewModel();
    }
}

Ну и напоследок: почему? Почему используются две разных схемы.

Ответ прост: первый случай, контроллер, хорош для сильного разделения на «умный сервер» и «быстрое, но мало думающее, отображение». Это именно тот случай, когда сервер и интерфейс далеко друг от друга, а связь между ними слабая — типично для Web-приложений.

Ну а второй случай, View-Model, удобен для прозрачного и простого разделения логики и отображения для каждого из элементов, когда есть «много каналов связи», то есть для desktop или мобильных приложений.

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *