← Назад к вопросам

Какие архитектурные паттерны используются во Flutter (MVP, MVVM, Clean Architecture)?

3.0 Senior🔥 282 комментариев
#Архитектура Flutter#ООП и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектурные паттерны во Flutter

Архитектура определяет качество, масштабируемость и maintainability проекта. Рассмотрю три основных подхода.

1. MVC (Model-View-Controller) — старый подход

Одна из первых архитектур, но во Flutter редко используется.

┌─────────────┐
│  Controller │
│   (Logic)   │
└──────┬──────┘
       │
       ├──→ Model (State, Data)
       └──→ View (UI)

Проблемы:
- Controller быстро становится жирным
- Сложно тестировать UI
- Tight coupling между компонентами

2. MVP (Model-View-Presenter) — лучше, но громоздко

┌─────────────────────────────────┐
│         Presenter               │
│  (Business Logic & UI Logic)    │
└─────────────────────────────────┘
    ↓ implements          ↑ updates
┌─────────────┐       ┌──────────┐
│   View      │       │  Model   │
│  (UI только)│       │ (Data)   │
└─────────────┘       └──────────┘

Примеры:
- User taps button
  → View notifies Presenter
  → Presenter updates Model
  → Model notifies View
  → View rebuilds
// Контракт между View и Presenter
abstract class LoginView {
  void showProgress();
  void hideProgress();
  void showError(String message);
  void navigateToHome();
}

class LoginPresenter {
  final LoginView view;
  final AuthRepository repository;
  
  LoginPresenter({required this.view, required this.repository});
  
  Future<void> login(String email, String password) async {
    view.showProgress();
    try {
      final result = await repository.login(email, password);
      view.hideProgress();
      view.navigateToHome();
    } catch (e) {
      view.hideProgress();
      view.showError(e.toString());
    }
  }
}

// View реализует контракт
class LoginScreen extends StatefulWidget {
  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> implements LoginView {
  late LoginPresenter presenter;
  bool isLoading = false;
  
  @override
  void initState() {
    super.initState();
    presenter = LoginPresenter(
      view: this,
      repository: AuthRepository(),
    );
  }
  
  @override
  void showProgress() {
    setState(() => isLoading = true);
  }
  
  @override
  void hideProgress() {
    setState(() => isLoading = false);
  }
  
  @override
  void showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }
  
  @override
  void navigateToHome() {
    Navigator.of(context).pushReplacementNamed('/home');
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: isLoading
          ? CircularProgressIndicator()
          : ElevatedButton(
              onPressed: () => presenter.login('test@test.com', 'password'),
              child: Text('Login'),
            ),
    );
  }
}

Минусы: Много boilerplate кода, interface для каждого экрана.

3. MVVM (Model-View-ViewModel) — модерный подход

ВViewModel хранится бизнес-логика и состояние, View только отображает.

┌──────────────────────────────┐
│    ViewModel                 │
│  (State + Business Logic)    │
│  Notifies listeners          │
└──────────────────────────────┘
      ↑             ↓
    Watch        Update
      ↑             ↓
┌──────────────────────────────┐
│    View (UI)                 │
│    Rebuilds on state change  │
└──────────────────────────────┘
      ↓
┌──────────────────────────────┐
│    Model (Data)              │
└──────────────────────────────┘
// ViewModel с Provider
class LoginViewModel extends ChangeNotifier {
  final AuthRepository repository;
  
  String? _error;
  bool _isLoading = false;
  
  LoginViewModel({required this.repository});
  
  String? get error => _error;
  bool get isLoading => _isLoading;
  
  Future<void> login(String email, String password) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      await repository.login(email, password);
      _isLoading = false;
      notifyListeners();
    } catch (e) {
      _isLoading = false;
      _error = e.toString();
      notifyListeners();
    }
  }
}

// View просто отображает состояние
class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer<LoginViewModel>(
        builder: (context, viewModel, child) {
          if (viewModel.isLoading) {
            return Center(child: CircularProgressIndicator());
          }
          
          return Column(
            children: [
              if (viewModel.error != null)
                Text('Ошибка: ${viewModel.error}', style: TextStyle(color: Colors.red)),
              ElevatedButton(
                onPressed: () => viewModel.login('test@test.com', 'password'),
                child: Text('Login'),
              ),
            ],
          );
        },
      ),
    );
  }
}

4. Clean Architecture — enterprise подход

┌─────────────────────────────────┐
│    Presentation Layer           │ ← UI, Pages, Widgets
│ (Views, ViewModels)             │
└──────────────┬──────────────────┘
               ↓ depends on
┌─────────────────────────────────┐
│    Application Layer            │ ← Use Cases, DTOs
│ (Repositories, Services)        │
└──────────────┬──────────────────┘
               ↓ depends on
┌─────────────────────────────────┐
│    Domain Layer                 │ ← Business Rules
│ (Entities, Interfaces)          │
└──────────────┬──────────────────┘
               ↓ depends on
┌─────────────────────────────────┐
│    Infrastructure Layer         │ ← DB, API, External Services
│ (Data Sources, Adapters)        │
└─────────────────────────────────┘

Правило: зависимости ТОЛЬКО вниз
presentation ← application ← domain → infrastructure
// lib/domain/entities/user.dart
class User {
  final int id;
  final String name;
  final String email;
  
  User({required this.id, required this.name, required this.email});
}

// lib/domain/repositories/user_repository.dart
abstract class UserRepository {
  Future<User> getUser(int id);
  Future<void> updateUser(User user);
}

// lib/domain/use_cases/get_user_use_case.dart
class GetUserUseCase {
  final UserRepository repository;
  
  GetUserUseCase({required this.repository});
  
  Future<User> call(int id) async {
    return await repository.getUser(id);
  }
}

// lib/infrastructure/data_sources/user_remote_data_source.dart
class UserRemoteDataSource {
  final http.Client httpClient;
  
  Future<UserModel> getUser(int id) async {
    final response = await httpClient.get(Uri.parse('...'));
    return UserModel.fromJson(jsonDecode(response.body));
  }
}

// lib/infrastructure/repositories/user_repository_impl.dart
class UserRepositoryImpl implements UserRepository {
  final UserRemoteDataSource remoteDataSource;
  
  @override
  Future<User> getUser(int id) async {
    return await remoteDataSource.getUser(id);
  }
}

// lib/presentation/view_models/user_view_model.dart
class UserViewModel extends ChangeNotifier {
  final GetUserUseCase getUserUseCase;
  
  User? _user;
  bool _isLoading = false;
  
  Future<void> loadUser(int id) async {
    _isLoading = true;
    notifyListeners();
    try {
      _user = await getUserUseCase(id);
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

Сравнение

КритерийMVPMVVMClean
СложностьСредняяПростаяВысокая
BoilerplateМногоМалоМного
ТестируемостьХорошаяОтличнаяОтличная
МасштабируемостьСреднееХорошееОтличное
Кривая обученияСредняяНизкаяВысокая

Рекомендации для production

Стартапы / MVPs: MVVM + Provider

  • Быстро разработать
  • Легко добавить тесты
  • Понятный код

Enterprise проекты: Clean Architecture + Riverpod

  • Масштабируемость
  • Независимость слоёв
  • Простота тестирования

Средние проекты: MVVM + GetX или Riverpod

  • Баланс между простотой и масштабируемостью