Ответ
Service Locator (Локатор служб) — это паттерн, предоставляющий глобальный реестр для доступа к сервисам (зависимостям) приложения, таким как API-клиенты, репозитории, настройки. Вместо того чтобы передавать зависимости через конструкторы по всему дереву виджетов (как при Dependency Injection), мы "локализуем" их в одном месте.
Популярная реализация во Flutter — пакет get_it:
-
Настройка (регистрация зависимостей):
// service_locator.dart import 'package:get_it/get_it.dart'; final getIt = GetIt.instance; void setupLocator() { // Регистрируем синглтон (один экземпляр на все приложение) getIt.registerSingleton<ApiClient>(ApiClient()); // Регистрируем фабрику (новый экземпляр при каждом запросе) getIt.registerFactory<LoginBloc>(() => LoginBloc()); // Регистрируем ленивый синглтон (создается при первом запросе) getIt.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl()); // Регистрация с зависимостью getIt.registerSingleton<UserService>(UserService(getIt<ApiClient>())); } -
Использование в коде:
// В бизнес-логике (Cubit/BLoC) class UserCubit extends Cubit<UserState> { final UserService _userService = getIt<UserService>(); // Получаем сервис UserCubit() : super(UserInitial()); void loadUser() async { final user = await _userService.getCurrentUser(); emit(UserLoaded(user)); } } // В UI (если нужно, но лучше избегать) Widget build(BuildContext context) { final authService = getIt<AuthRepository>(); return Text('User: ${authService.currentUser?.name}'); }
Плюсы:
- Простота: Легко начать использовать, не требуется оборачивать все приложение в провайдеры.
- Глобальный доступ: Удобен для сервисов, которые нужны "везде" (навигация, аналитика).
- Упрощает навигацию: Легко получить доступ к BloC из другого экрана.
Минусы и предостережения:
- Скрытые зависимости: Код становится менее явным, сложнее отследить, от чего зависит класс.
- Усложнение тестирования: Требуется сбрасывать или переопределять зависимости в локаторе перед каждым тестом.
- Нарушение инверсии зависимостей: Классы зависят от конкретной реализации локатора, а не от абстракций.
Когда использовать? Для небольших проектов, прототипов или для истинно глобальных сервисов (например, FirebaseAnalytics). Для больших, тестируемых приложений предпочтительнее использовать внедрение зависимостей через конструктор в сочетании с пакетами типа provider или riverpod, которые обеспечивают лучшую управляемость и тестируемость.
Ответ 18+ 🔞
Ёпта, ну слушай, вот есть такая штука — Service Locator (Локатор служб). Представь себе, что это такой глобальный склад, куда ты сваливаешь все свои штуки-дрюки: разные клиенты к API, репозитории, настройки — всё, что душе угодно. Вместо того чтобы таскать эти коробки с зависимостями через все конструкторы виджетов (как при Dependency Injection), ты просто кидаешь их в один угол и потом оттуда берёшь. Удобно, да? Ну, как посмотреть.
Вот, например, как это часто делают во Flutter — через пакет get_it:
-
Сначала всё настраиваем и регистрируем (разгружаем фуру на склад):
// service_locator.dart import 'package:get_it/get_it.dart'; final getIt = GetIt.instance; void setupLocator() { // Регистрируем синглтон — одна банка варенья на всех, больше не сделаем getIt.registerSingleton<ApiClient>(ApiClient()); // Регистрируем фабрику — каждый раз новая свежая булка getIt.registerFactory<LoginBloc>(() => LoginBloc()); // Регистрируем ленивый синглтон — сделаем, но только когда первый раз попросят, а до этого пусть лежит getIt.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl()); // А тут регистрация с зависимостью — одна штука требует другую штуку getIt.registerSingleton<UserService>(UserService(getIt<ApiClient>())); } -
А потом в коде просто берём, что надо (пришёл на склад и взял с полки):
// В бизнес-логике (Cubit/BLoC) class UserCubit extends Cubit<UserState> { final UserService _userService = getIt<UserService>(); // Достал сервис UserCubit() : super(UserInitial()); void loadUser() async { final user = await _userService.getCurrentUser(); emit(UserLoaded(user)); } } // В UI (хотя лучше так не делать, но если очень хочется...) Widget build(BuildContext context) { final authService = getIt<AuthRepository>(); return Text('User: ${authService.currentUser?.name}'); }
Плюсы, блядь:
- Простота — Разобраться проще пареной репы. Не надо оборачивать всё приложение в провайдеры, как капустный пирог.
- Глобальный доступ — Идеально для сервисов, которые нужны везде и сразу: навигация, аналитика... Ну, ты понял.
- Упрощает навигацию — Захотел получить доступ к BloC с другого экрана? Да похуй, просто достал из локатора. Ебушки-воробушки, и никаких проблем.
Минусы и подводные камни, ёпта:
- Скрытые зависимости — Код превращается в магию. Смотришь на класс и нихуя не поймёшь, от чего он там зависит. Доверия ебать ноль к такой архитектуре.
- Усложнение тестирования — Перед каждым тестом надо этот склад либо обнулять, либо подсовывать поддельные штуки. Геморрой ещё тот.
- Нарушение инверсии зависимостей — Классы начинают зависеть не от абстракций, а от конкретной конторы под названием
getIt. Это, чувак, пиздопроебибна идея для масштабирования.
Так когда же это использовать? Для мелких проектов, прототипов или для тех сервисов, которые реально глобальные — типа FirebaseAnalytics. Но если делаешь что-то серьёзное и тестируемое, то лучше использовать нормальное внедрение зависимостей через конструктор с provider или riverpod. Там управляемость и тестируемость — просто овердохуища. А локатор — он как хуй в пальто: вроде и есть, а толку...