Ответ
В Flutter-разработке оба подхода решают задачу управления зависимостями, но с разной философией и последствиями для тестируемости и поддерживаемости кода.
Dependency Injection (Внедрение зависимостей):
Зависимости явно передаются в класс извне, обычно через конструктор:
// Слой данных
class AuthRepository {
Future<User> login(String email, String password) async {
// Реализация логина
}
}
// Слой бизнес-логики с DI
class LoginService {
final AuthRepository _authRepo;
final AnalyticsService _analytics;
// Зависимости явно указаны в конструкторе
LoginService(this._authRepo, this._analytics);
Future<void> performLogin(String email, String password) async {
final user = await _authRepo.login(email, password);
await _analytics.trackLogin(user.id);
}
}
// Использование с пакетом get_it (как DI-контейнер)
final getIt = GetIt.instance;
getIt.registerFactory<AuthRepository>(() => AuthRepository());
getIt.registerFactory<AnalyticsService>(() => FirebaseAnalyticsService());
getIt.registerFactoryParam<LoginService, String, String>(
(email, password) => LoginService(
getIt<AuthRepository>(),
getIt<AnalyticsService>(),
),
);
Service Locator (Локатор служб):
Класс сам запрашивает зависимости из глобального реестра:
// Тот же LoginService, но с Service Locator
class LoginService {
// Зависимости скрыты - их получение внутри методов
Future<void> performLogin(String email, String password) async {
final authRepo = GetIt.instance<AuthRepository>(); // Явный запрос
final analytics = GetIt.instance<AnalyticsService>();
final user = await authRepo.login(email, password);
await analytics.trackLogin(user.id);
}
}
Ключевые различия:
| Аспект | Dependency Injection | Service Locator |
|---|---|---|
| Явность зависимостей | Видны в сигнатуре класса | Скрыты внутри реализации |
| Тестируемость | Легко мокировать через конструктор | Требует настройки глобального локатора |
| Связность кода | Низкая — зависимости извне | Высокая — привязка к глобальному локатору |
| Читаемость | Понятно, что нужно классу | Неясно без изучения кода методов |
Мой подход: В production-проектах я предпочитаю чистый DI через конструктор с использованием get_it как DI-контейнера, но не как Service Locator. Для этого использую иерархическую регистрацию:
// Регистрация зависимостей
void setupDependencies() {
// Репозитории
getIt.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl());
// Сервисы
getIt.registerLazySingleton<LoginService>(
() => LoginService(getIt<AuthRepository>()), // Явная передача
);
// ViewModel с автоматической инъекцией
getIt.registerFactory<LoginViewModel>(
() => LoginViewModel(getIt<LoginService>()),
);
}
// В виджете
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
// Получаем ViewModel с уже внедренными зависимостями
final viewModel = getIt<LoginViewModel>();
return LoginView(viewModel: viewModel);
}
}
Это обеспечивает лучшую тестируемость — в тестах я могу передать моки напрямую в конструктор, не настраивая глобальное состояние.