Что такое Service Locator (локатор служб) и как он используется во Flutter?

«Что такое Service Locator (локатор служб) и как он используется во Flutter?» — вопрос из категории DI, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Service Locator (Локатор служб) — это паттерн, предоставляющий глобальный реестр для доступа к сервисам (зависимостям) приложения, таким как API-клиенты, репозитории, настройки. Вместо того чтобы передавать зависимости через конструкторы по всему дереву виджетов (как при Dependency Injection), мы "локализуем" их в одном месте.

Популярная реализация во Flutter — пакет get_it:

  1. Настройка (регистрация зависимостей):

    // 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>()));
    }
  2. Использование в коде:

    // В бизнес-логике (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, которые обеспечивают лучшую управляемость и тестируемость.