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

Что такое useCase?

1.3 Junior🔥 151 комментариев
#Архитектура Flutter

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

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

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

UseCase в Clean Architecture

UseCase — это ключевой слой в Clean Architecture, который инкапсулирует бизнес-логику приложения. Это один из самых важных паттернов, которые я использую в профессиональной разработке.

Определение

UseCase (также называется Interactor) — это класс, который:
  • Инкапсулирует одну бизнес-операцию
  • Независим от деталей реализации (UI, БД, сеть)
  • Легко тестируется в изоляции
  • Переиспользуется в разных местах приложения

Архитектурный слой

┌─────────────────────────┐
│   Presentation (UI)     │  ← Widgets, Screens, BLoC
├─────────────────────────┤
│   Domain (UseCase)      │  ← Бизнес-логика
├─────────────────────────┤
│   Data (Repository)     │  ← Источники данных
└─────────────────────────┘

UseCase находится в Domain слое — самом чистом и независимом от фреймворков.

Простой пример

// domain/repositories/user_repository.dart
abstract class UserRepository {
  Future<User> getUserById(String id);
  Future<void> saveUser(User user);
}

// domain/entities/user.dart
class User {
  final String id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });
}

// domain/usecases/get_user_usecase.dart
class GetUserUseCase {
  final UserRepository repository;

  GetUserUseCase(this.repository);

  Future<User> call(String userId) async {
    // Бизнес-логика
    if (userId.isEmpty) {
      throw ArgumentError('User ID cannot be empty');
    }
    
    final user = await repository.getUserById(userId);
    return user;
  }
}

// Использование в BLoC
class UserBloc extends Bloc<UserEvent, UserState> {
  final GetUserUseCase getUserUseCase;

  UserBloc({required this.getUserUseCase}) : super(UserInitial()) {
    on<FetchUserEvent>((event, emit) async {
      emit(UserLoading());
      try {
        final user = await getUserUseCase(event.userId);
        emit(UserLoaded(user));
      } catch (e) {
        emit(UserError(e.toString()));
      }
    });
  }
}

Паттерн: UseCase со входными параметрами

// domain/usecases/params/get_user_params.dart
class GetUserParams {
  final String userId;
  final bool includeDetails;

  GetUserParams({
    required this.userId,
    this.includeDetails = false,
  });
}

// domain/usecases/get_user_with_params_usecase.dart
class GetUserWithParamsUseCase {
  final UserRepository repository;

  GetUserWithParamsUseCase(this.repository);

  Future<User> call(GetUserParams params) async {
    if (params.userId.isEmpty) {
      throw ArgumentError('User ID cannot be empty');
    }
    
    var user = await repository.getUserById(params.userId);
    
    if (params.includeDetails) {
      // Дополнительная обработка
      user = await _enrichUserDetails(user);
    }
    
    return user;
  }

  Future<User> _enrichUserDetails(User user) async {
    // Получи дополнительные данные
    return user;
  }
}

// Использование
final useCase = GetUserWithParamsUseCase(repository);
final user = await useCase(GetUserParams(
  userId: '123',
  includeDetails: true,
));

Сложный пример: Трансфер денег

// domain/repositories/account_repository.dart
abstract class AccountRepository {
  Future<Account> getAccount(String accountId);
  Future<void> updateAccount(Account account);
}

// domain/usecases/transfer_money_usecase.dart
class TransferMoneyUseCase {
  final AccountRepository accountRepository;

  TransferMoneyUseCase(this.accountRepository);

  Future<void> call({
    required String fromAccountId,
    required String toAccountId,
    required double amount,
  }) async {
    // Валидация
    if (amount <= 0) {
      throw ArgumentError('Amount must be greater than 0');
    }

    // Получи оба счета
    final fromAccount = await accountRepository.getAccount(fromAccountId);
    final toAccount = await accountRepository.getAccount(toAccountId);

    // Проверь баланс
    if (fromAccount.balance < amount) {
      throw InsufficientFundsException('Not enough balance');
    }

    // Проверь статус счетов
    if (!fromAccount.isActive || !toAccount.isActive) {
      throw InactiveAccountException('Account is inactive');
    }

    // Выполни трансфер
    fromAccount.balance -= amount;
    toAccount.balance += amount;
    fromAccount.lastTransaction = DateTime.now();
    toAccount.lastTransaction = DateTime.now();

    // Сохрани оба счета
    await accountRepository.updateAccount(fromAccount);
    await accountRepository.updateAccount(toAccount);
  }
}

Паттерн: UseCase c результатом Either

Для обработки ошибок более функционально используй dartz package:

import 'package:dartz/dartz.dart';

// domain/failures/failures.dart
abstract class Failure {}

class NetworkFailure extends Failure {}
class ValidationFailure extends Failure {}
class ServerFailure extends Failure {}

// domain/usecases/get_user_either_usecase.dart
class GetUserEitherUseCase {
  final UserRepository repository;

  GetUserEitherUseCase(this.repository);

  // Either<Failure, Success>
  Future<Either<Failure, User>> call(String userId) async {
    try {
      if (userId.isEmpty) {
        return Left(ValidationFailure());
      }
      
      final user = await repository.getUserById(userId);
      return Right(user);
    } catch (e) {
      return Left(NetworkFailure());
    }
  }
}

// Использование в BLoC
final result = await getUserEitherUseCase(userId);
result.fold(
  (failure) => emit(UserError('Failed to load user')),
  (user) => emit(UserLoaded(user)),
);

Структура проекта

lib/
├── domain/
│   ├── entities/
│   │   ├── user.dart
│   │   └── post.dart
│   ├── repositories/
│   │   ├── user_repository.dart
│   │   └── post_repository.dart
│   └── usecases/
│       ├── get_user_usecase.dart
│       ├── create_user_usecase.dart
│       ├── get_posts_usecase.dart
│       └── params/
│           ├── get_user_params.dart
│           └── get_posts_params.dart
├── data/
│   ├── datasources/
│   │   ├── user_local_datasource.dart
│   │   └── user_remote_datasource.dart
│   ├── repositories/
│   │   └── user_repository_impl.dart
│   └── models/
│       └── user_model.dart
└── presentation/
    ├── bloc/
    │   └── user_bloc.dart
    ├── pages/
    │   └── user_page.dart
    └── widgets/
        └── user_widget.dart

Тестирование UseCase

test('GetUserUseCase returns user when repository succeeds', () async {
  // Arrange
  final mockRepository = MockUserRepository();
  when(mockRepository.getUserById('123')).thenAnswer(
    (_) async => User(id: '123', name: 'John', email: 'john@test.com'),
  );
  
  final useCase = GetUserUseCase(mockRepository);

  // Act
  final user = await useCase('123');

  // Assert
  expect(user.id, '123');
  expect(user.name, 'John');
  verify(mockRepository.getUserById('123')).called(1);
});

test('GetUserUseCase throws error when repository fails', () async {
  // Arrange
  final mockRepository = MockUserRepository();
  when(mockRepository.getUserById('')).thenThrow(ArgumentError());
  
  final useCase = GetUserUseCase(mockRepository);

  // Act & Assert
  expect(() => useCase(''), throwsArgumentError);
});

Преимущества UseCase

Тестируемость — легко писать unit tests в изоляции ✅ Переиспользуемость — один UseCase, несколько UI экранов ✅ Чистота кода — бизнес-логика отделена от деталей ✅ Независимость — UseCase не знает про Flutter, BLoC, HTTP ✅ Масштабируемость — легко добавлять новые фичи ✅ Документация — UseCase — это договор между слоями

Антипаттерны

UseCase с UI логикой

// Плохо
class GetUserUseCase {
  Future<void> call(String userId) async {
    final user = await repository.getUserById(userId);
    showSnackBar('User loaded!'); // UI логика в UseCase!
  }
}

Слишком тонкий UseCase

// Плохо
class GetUserUseCase {
  Future<User> call(String userId) async {
    return await repository.getUserById(userId); // Просто проксирует!
  }
}

UseCase без инъекции зависимостей

// Плохо
class GetUserUseCase {
  final repository = UserRepositoryImpl(); // Hardcoded!
}

Заключение

UseCase — это фундамент Clean Architecture. За 10+ лет я вижу, что проекты с хорошо спроектированными UseCases:

  • Работают дольше без переписывания
  • Легче масштабируются
  • Проще тестируются
  • Быстрее разрабатываются благодаря переиспользованию

Это инвестиция в качество, которая окупается быстро.

Что такое useCase? | PrepBro