Что такое Use Case в контексте архитектуры Flutter-приложения?

Ответ

В архитектуре Flutter-приложения, например, при использовании Clean Architecture, Use Case (или Interactor) — это класс, который инкапсулирует одну конкретную бизнес-правило или действие. Он находится в слое домена и координирует поток данных между репозиториями (данные) и презентерами/блоками (UI).

Пример Use Case для аутентификации пользователя:

// domain/use_cases/login_user_use_case.dart
import '../repositories/auth_repository.dart';

class LoginUserUseCase {
  final AuthRepository _authRepository;

  LoginUserUseCase(this._authRepository);

  // Выполняет конкретное бизнес-действие
  Future<void> execute({required String email, required String password}) async {
    // Валидация может быть здесь или в отдельном ValueObject
    if (email.isEmpty || password.isEmpty) {
      throw const FormatException('Email and password cannot be empty');
    }
    // Делегируем фактическую логику аутентификации репозиторию
    await _authRepository.login(email: email, password: password);
  }
}

Как это используется в слое презентации (например, с Bloc):

// presentation/login/bloc/login_bloc.dart
class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final LoginUserUseCase _loginUserUseCase;

  LoginBloc(this._loginUserUseCase) : super(LoginInitial()) {
    on<LoginButtonPressed>((event, emit) async {
      emit(LoginLoading());
      try {
        await _loginUserUseCase.execute(
          email: event.email,
          password: event.password,
        );
        emit(LoginSuccess());
      } catch (e) {
        emit(LoginFailure(error: e.toString()));
      }
    });
  }
}

Преимущества такого подхода:

  • Чистое разделение ответственности: UI не знает, откуда берутся данные (сеть, локальная БД).
  • Простота тестирования: Use Case можно легко протестировать, подменив AuthRepository моком.
  • Повторное использование: Одна бизнес-логика может быть использована в разных местах приложения (например, в мобильном и веб-интерфейсе).
  • Ясность: Каждый Use Case соответствует одной пользовательской истории.

Ответ 18+ 🔞

Слушай, а вот этот ваш Clean Architecture — это вообще ёперный театр какой-то. Ну ладно, объясню на пальцах, как будто ты только вчера из деревни приехал и Flutter для тебя — это про птиц.

Представь, у тебя есть приложение. В нём куча всего происходит: пользователь тыкает кнопки, данные откуда-то прилетают, куда-то улетают. Так вот, чтобы в этом всём не захлебнуться как последний бздун, умные дядьки придумали слои. И в самом центре, в святая святых, в домене, живут Use Case'ы.

Что это за зверь? Use Case — это, блядь, конкретное дело. Одно. Не «сделай всё», а «сделай вот это». Например, «залогинь пользователя». Всё. Больше он нихуя не должен делать. Это не бог и не царь, а просто работяга, который знает, кого попросить выполнить его маленькую задачку.

Вот смотри, как это выглядит в коде. Не пугайся, я поясню.

// domain/use_cases/login_user_use_case.dart
import '../repositories/auth_repository.dart';

class LoginUserUseCase {
  final AuthRepository _authRepository;

  LoginUserUseCase(this._authRepository);

  Future<void> execute({required String email, required String password}) async {
    // Сначала проверим, че нам пришло. А то вдруг пустые поля?
    if (email.isEmpty || password.isEmpty) {
      throw const FormatException('Email and password cannot be empty');
    }
    // А если всё ок — кидаем эту обузу на репозиторий. Пусть он парится.
    await _authRepository.login(email: email, password: password);
  }
}

Видишь? Весь его гениальный план — спросить у репозитория: «Э, сабака сука, залогинь-ка этого чела». Сам он в сеть не лезет, в базу не тыкается — у него для этого есть специально обученный AuthRepository. Use Case просто координирует, как менеджер-полупидор, который только и знает, что говорить «сделай».

А теперь, как этим пользуются наверху, в презентационном слое, где вся красота и кнопки мигают. Допустим, у нас Bloc.

// presentation/login/bloc/login_bloc.dart
class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final LoginUserUseCase _loginUserUseCase;

  LoginBloc(this._loginUserUseCase) : super(LoginInitial()) {
    on<LoginButtonPressed>((event, emit) async {
      emit(LoginLoading());
      try {
        // Всё! Блоку похуй, КАК логиниться. Он просто говорит Use Case: "Выполняй!"
        await _loginUserUseCase.execute(
          email: event.email,
          password: event.password,
        );
        emit(LoginSuccess());
      } catch (e) {
        emit(LoginFailure(error: e.toString()));
      }
    });
  }
}

Блок, по сути, тупая мартышлюшка: нажали кнопку — он вызвал Use Case. Получилось — радуется, не получилось — плачет. Он даже не в курсе, логинимся мы через Firebase, через свой бэкенд или через телепатию. Это же, бля, овердохуища удобно!

Итак, нахуя это всё? Давай списком, а то у тебя уже волнение ебать в глазах:

  1. Разделение ответственности. UI — это UI. Данные — это данные. Use Case — это мостик между ними, который не даёт им спутаться в грешном теле. UI не лезет в базу, как хитрая жопа, а репозиторий не рисует кнопки.
  2. Тестирование — просто пиздец как легко. Хочешь потестить Use Case? Подсунул ему фейковый репозиторий-муляж и смотришь, как он себя ведёт. Никаких реальных сетевых запросов, всё чисто и быстро.
  3. Повторное использование. Написал Use Case «получить погоду» — и тащи его и в телефон, и в веб, и в чайник умный. Бизнес-логика одна, а интерфейсы могут быть какие угодно.
  4. Ясность, мать её. Открываешь папку use_cases и видишь: LoginUserUseCase, FetchWeatherUseCase, BuyCoffeeUseCase. Сразу понятно, что это приложение умеет делать. Прямо как список дел на холодильнике, только для кода.

Короче, Use Case — это не какая-то мудрёная хуйня, а просто способ не превратить свой код в большую, вонючую кучу, где всё зависит ото всего. Попробуй, а то потом сам от себя охуеешь, когда начнёшь рефакторить свой старый спагетти-код.