Ответ
Чистая архитектура (Clean Architecture) — это подход к проектированию, который разделяет код на слои с четкими правилами зависимостей, направленными внутрь, к домену. Главная цель — создать приложение, которое:
- Не зависит от фреймворков (Flutter, базы данных, внешние библиотеки).
- Легко тестируется (бизнес-правила можно тестировать без UI, базы данных или сети).
- Просто в поддержке и развитии благодаря низкой связанности компонентов.
Типичные слои в Flutter-проекте:
- Domain (Домен): Ядро приложения. Содержит бизнес-сущности (
Entities) и сценарии использования (Use Cases/Interactors). Не зависит ни от чего внешнего. - Data (Данные): Реализует контракты, определенные в домене (репозитории). Работает с источниками данных (API, локальная БД). Зависит только от Domain-слоя.
- Presentation (Представление): Слой UI (виджеты) и управления состоянием (BLoC, Cubit, Provider). Зависит от Domain-слоя (Use Cases) и иногда от Data (через Dependency Injection).
Пример структуры для функции "получить пользователя":
1. Domain Layer (lib/domain/):
// Сущность (Entity)
class User {
final String id;
final String name;
final String email;
const User({required this.id, required this.name, required this.email});
}
// Абстракция репозитория (контракт)
abstract class UserRepository {
Future<User> getUserById(String id);
}
// Сценарий использования (Use Case)
class GetUserUseCase {
final UserRepository repository;
GetUserUseCase(this.repository);
Future<User> execute(String userId) async {
return await repository.getUserById(userId);
}
}
2. Data Layer (lib/data/):
// Модель данных для API (DTO - Data Transfer Object)
class UserDto {
final String id;
final String full_name; // Может отличаться от имени в Entity
final String email;
UserDto({required this.id, required this.full_name, required this.email});
// Конвертация из DTO в Entity
User toEntity() {
return User(id: id, name: full_name, email: email);
}
}
// Конкретная реализация репозитория
class UserRepositoryImpl implements UserRepository {
final UserApiService apiService;
UserRepositoryImpl(this.apiService);
@override
Future<User> getUserById(String id) async {
final UserDto dto = await apiService.fetchUser(id);
return dto.toEntity(); // Преобразуем данные API в доменную сущность
}
}
3. Presentation Layer (lib/presentation/):
// BLoC для управления состоянием экрана
class UserBloc extends Bloc<UserEvent, UserState> {
final GetUserUseCase getUserUseCase; // Зависимость от Use Case
UserBloc(this.getUserUseCase) : super(UserInitial()) {
on<FetchUserEvent>((event, emit) async {
emit(UserLoading());
try {
final user = await getUserUseCase.execute(event.userId);
emit(UserLoaded(user));
} catch (e) {
emit(UserError(e.toString()));
}
});
}
}
// Виджет, использующий BLoC
class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoaded) {
return Text('Пользователь: ${state.user.name}');
}
// ... обработка других состояний (загрузка, ошибка)
},
);
}
}
Такой подход требует больше кода на старте, но окупается на больших и долгоживущих проектах за счет предсказуемости и простоты внесения изменений.
Видео-ответы
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶
▶