Зачем нужны архитектурные слои в Flutter?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
Архитектурные слои в Flutter — это структурное разделение приложения на отдельные уровни абстракции, каждый из которых отвечает за конкретную функциональность. Классическая архитектура включает слои: presentation, domain, application и infrastructure. Это критически важно для создания масштабируемых, поддерживаемых и тестируемых приложений.
Основные проблемы без архитектурных слоёв
// Плохо — всё смешано в одном месте
class UserProfilePage extends StatefulWidget {
@override
State<UserProfilePage> createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
late User user;
@override
void initState() {
super.initState();
// Бизнес-логика прямо в UI
final dio = Dio();
dio.get("/api/users/me").then((response) {
final db = Database();
final user = User.fromJson(response.data);
db.save(user); // сохранение в БД тут же
setState(() => this.user = user);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Text(user.name),
);
}
}
Классическая многослойная архитектура
┌─────────────────────────────────────┐
│ Presentation Layer (UI) │
│ Виджеты, экраны, управление │
└────────────────┬────────────────────┘
│ зависит от
┌─────────────────▼────────────────────┐
│ Application Layer (UseCase) │
│ Бизнес-логика, часто используемые │
│ операции, состояние приложения │
└────────────────┬────────────────────┘
│ зависит от
┌─────────────────▼────────────────────┐
│ Domain Layer (Entity, Interface) │
│ Чистая бизнес-логика, модели, │
│ интерфейсы репозиториев │
└────────────────┬────────────────────┘
│ зависит от
┌─────────────────▼────────────────────┐
│ Infrastructure Layer (реализация) │
│ Сеть, БД, внешние сервисы │
└─────────────────────────────────────┘
Паттерн Clean Architecture
// Domain слой — не зависит ни от чего
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
abstract class UserRepository {
Future<User> getUserById(String id);
Future<void> saveUser(User user);
}
// Application слой — содержит use cases
class GetUserUseCase {
final UserRepository repository;
GetUserUseCase(this.repository);
Future<User> execute(String userId) {
return repository.getUserById(userId);
}
}
// Infrastructure слой — конкретная реализация
class UserRepositoryImpl implements UserRepository {
final Dio httpClient;
final LocalDatabase database;
UserRepositoryImpl(this.httpClient, this.database);
@override
Future<User> getUserById(String id) async {
try {
final response = await httpClient.get("/api/users/$id");
final user = User.fromJson(response.data);
await database.saveUser(user);
return user;
} catch (e) {
return database.getUserById(id);
}
}
@override
Future<void> saveUser(User user) => database.saveUser(user);
}
// Presentation слой — только UI логика
class UserProfilePage extends ConsumerWidget {
final String userId;
const UserProfilePage({required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsyncValue = ref.watch(getUserProvider(userId));
return userAsyncValue.when(
data: (user) => Scaffold(
body: Text(user.name),
),
loading: () => const CircularProgressIndicator(),
error: (error, _) => Text("Error: $error"),
);
}
}
Преимущества архитектурных слоёв
Разделение ответственности — каждый слой знает только о своей зоне:
// UI слой не знает об HTTP и БД
class Button extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const Button({required this.label, required this.onPressed});
@override
Widget build(BuildContext context) => TextButton(
onPressed: onPressed,
child: Text(label),
);
}
Тестируемость — легко писать unit тесты:
void main() {
group("GetUserUseCase", () {
test("returns user from repository", () async {
final mockRepo = MockUserRepository();
when(mockRepo.getUserById("123"))
.thenAnswer((_) async => User(id: "123", name: "John", email: "john@example.com"));
final useCase = GetUserUseCase(mockRepo);
final result = await useCase.execute("123");
expect(result.name, "John");
});
});
}
Масштабируемость — просто добавлять новую функциональность.
Переиспользуемость — один use case может использоваться разными экранами.
Гибкость — легко менять реализацию без изменения других слоёв.
Архитектурные слои — это не усложнение, а инвестиция в качество и долговечность кода.