Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
DI (Dependency Injection) — это архитектурный паттерн, который решает проблему создания и управления зависимостями (dependencies) в приложении. Вместо того чтобы объект создавал сам нужные ему зависимости, они передаются ему извне. Это делает код более модульным, тестируемым и гибким.
Проблема, которую решает DI
// Плохо — прямая зависимость, сложно тестировать
class UserRepository {
final database = DatabaseService(); // жёстко связано
Future<User> getUser(String id) {
return database.query("SELECT * FROM users WHERE id = $id");
}
}
// Сложно написать тест — нельзя подменить DatabaseService
Решение с DI
// Хорошо — зависимость передаётся в конструктор
class UserRepository {
final DatabaseService database;
UserRepository(this.database);
Future<User> getUser(String id) {
return database.query("SELECT * FROM users WHERE id = $id");
}
}
// Легко тестировать — передаём mock
class MockDatabase implements DatabaseService {
@override
Future query(String sql) async => User(id: "1", name: "John");
}
void main() {
final mockDb = MockDatabase();
final repo = UserRepository(mockDb);
expect(await repo.getUser("1"), isNotNull);
}
Три способа внедрения зависимостей
1. Constructor Injection (через конструктор) — самый распространённый и предпочтительный способ:
class AuthService {
final HttpClient httpClient;
final StorageService storage;
AuthService(this.httpClient, this.storage);
}
// Использование
final authService = AuthService(httpClient, storage);
2. Property Injection (через свойства) — менее предпочтительно, но иногда используется:
class AuthService {
late HttpClient httpClient;
late StorageService storage;
}
// Использование
final authService = AuthService();
authService.httpClient = httpClient;
authService.storage = storage;
3. Service Locator (через глобальный контейнер) — удобно, но может скрывать зависимости:
// Регистрация
final getIt = GetIt.instance;
getIt.registerSingleton<HttpClient>(HttpClient());
getIt.registerSingleton<AuthService>(AuthService());
// Использование
final authService = getIt<AuthService>();
DI контейнеры в Flutter
GetIt — простой и популярный сервис-локатор:
import "package:get_it/get_it.dart";
final getIt = GetIt.instance;
void setupDependencies() {
// Singleton — один экземпляр на всё приложение
getIt.registerSingleton<DatabaseService>(DatabaseService());
// Lazy singleton — создаётся только при первом использовании
getIt.registerLazySingleton<AuthService>(
() => AuthService(getIt<DatabaseService>()),
);
// Factory — новый экземпляр при каждом запросе
getIt.registerFactory<UserRepository>(
() => UserRepository(getIt<DatabaseService>()),
);
}
// Использование
void main() {
setupDependencies();
runApp(MyApp());
}
class UserService {
final repository = getIt<UserRepository>();
}
Riverpod — современный реактивный подход:
import "package:riverpod/riverpod.dart";
// Провайдеры зависимостей
final databaseProvider = Provider((ref) => DatabaseService());
final repositoryProvider = Provider((ref) {
return UserRepository(ref.watch(databaseProvider));
});
final userServiceProvider = Provider((ref) {
return UserService(ref.watch(repositoryProvider));
});
// Использование в виджете
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final service = ref.watch(userServiceProvider);
return Text(service.userName);
}
}
Преимущества DI
- Тестируемость — легко создавать mock объекты
- Гибкость — просто менять реализацию без изменения кода
- Модульность — слабая связанность компонентов
- Переиспользование — один класс работает с разными реализациями
- Читаемость — явно видны все зависимости
DI — это не опциональный паттерн, а стандарт профессиональной разработки, особенно в больших приложениях.