Что такое паттерн MVVM?

«Что такое паттерн MVVM?» — вопрос из категории Архитектура, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

MVVM (Model-View-ViewModel) — это архитектурный паттерн, который разделяет ответственность в приложении на три слоя для улучшения тестируемости и поддержки кода.

  • Model: Представляет бизнес-логику и данные приложения (например, классы домена, репозитории, источники данных).
  • View: Отвечает за отображение UI и обработку пользовательского ввода. Во Flutter это Widget (чаще StatelessWidget).
  • ViewModel: Выступает посредником между Model и View. Он содержит логику представления, преобразует данные из Model в форму, удобную для отображения в View, и часто является ChangeNotifier или используется с Stream/StateNotifier для реактивного обновления UI.

Преимущества во Flutter:

  • Чёткое разделение: Widget остаётся «глупым» и только рисует то, что ему передали.
  • Упрощённое тестирование: ViewModel можно тестировать отдельно от Flutter-виджетов.
  • Хорошо сочетается с пакетами для управления состоянием, такими как provider, riverpod, bloc (который можно рассматривать как вариацию ViewModel).

Пример реализации с ChangeNotifier (паттерн Provider):

// Model
class User {
  final String name;
  final int age;
  User({required this.name, required this.age});
}

// ViewModel
class UserViewModel extends ChangeNotifier {
  User _currentUser = User(name: 'Alice', age: 30);

  String get userName => _currentUser.name;
  String get userAge => '${_currentUser.age} years';

  void updateUserAge(int newAge) {
    _currentUser = User(name: _currentUser.name, age: newAge);
    notifyListeners(); // Сообщает View об изменении
  }
}

// View
class UserView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Используем Consumer для подписки на изменения ViewModel
    return Consumer<UserViewModel>(
      builder: (context, viewModel, child) {
        return Column(
          children: [
            Text('Name: ${viewModel.userName}'),
            Text('Age: ${viewModel.userAge}'),
            ElevatedButton(
              onPressed: () => viewModel.updateUserAge(31),
              child: Text('Have Birthday'),
            ),
          ],
        );
      },
    );
  }
}