Ответ
В 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);
}
}
Это обеспечивает лучшую тестируемость — в тестах я могу передать моки напрямую в конструктор, не настраивая глобальное состояние.
Ответ 18+ 🔞
Слушай, давай разберём эту дичь про зависимости во 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);
}
}
// Настройка DI-контейнера (возьмём get_it)
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 |
|---|---|---|
| Видимость зависимостей | Всё на виду, как в витрине | Спрятано, нихуя не понятно |
| Тестируемость | Легко как два пальца — суёшь моки в конструктор | Ёперный театр! Надо настраивать глобальный контейнер для каждого теста |
| Связанность кода | Слабая, класс не привязан к внешнему миру | Крепкая, как сука, к глобальному локатору |
| Понятность | Сразу видно, что классу нужно | Чтобы понять, надо копаться в методах — доверия ебать ноль |
Мой вердикт, блядь
В нормальных, продакшен-проектах я использую чистый DI через конструктор. GetIt беру только как контейнер для регистрации, а не как 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);
}
}
Почему так? Тестируемость, ёбана! В тестах я просто создаю сервис с моками, и мне да похуй на глобальное состояние. Не надо танцевать с бубном вокруг GetIt.instance. Код становится чище, прозрачнее, и когда через полгода надо что-то менять, ты не охуеешь от того, что зависимости раскиданы по всем методам, как тараканы по кухне.