← Назад к вопросам

Что такое сервис локатор?

1.7 Middle🔥 182 комментариев
#Архитектура Flutter#ООП и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Service Locator — централизованный реестр сервисов

Service Locator — это паттерн проектирования, который предоставляет центральный реестр для получения экземпляров сервисов и зависимостей. В Flutter чаще всего используется пакет GetIt для реализации этого паттерна.

Основная идея

Service Locator позволяет:

  • Централизованное управление зависимостями — один реестр для всего приложения
  • Ленивая инициализация — сервисы создаются при первом использовании
  • Глобальный доступ — из любого места приложения
  • Простая подмена — для тестирования легко заменить реальный сервис на mock
  • Декупление — классы не знают о способе создания зависимостей

GetIt в Flutter

Установка:

flutter pub add get_it

Базовое использование

import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

// Регистрация сервисов в main.dart
void setupServiceLocator() {
  // Singleton — один экземпляр на все приложение
  getIt.registerSingleton<ApiClient>(ApiClient());
  
  // Lazy singleton — создается при первом использовании
  getIt.registerLazySingleton<AuthService>(() => AuthService());
  
  // Factory — каждый раз новый экземпляр
  getIt.registerFactory<UserRepository>(() => UserRepository());
}

void main() {
  setupServiceLocator();
  runApp(MyApp());
}

// Использование в коде
class UserWidget extends StatelessWidget {
  final _authService = getIt<AuthService>();
  
  @override
  Widget build(BuildContext context) {
    return Text(_authService.currentUser.name);
  }
}

Практические примеры

1. Установка сервисов (Service Setup Layer):

// lib/config/service_locator.dart
import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setupServiceLocator() {
  // Data Layer
  getIt.registerLazySingleton<ApiClient>(
    () => ApiClient(baseUrl: 'https://api.example.com'),
  );
  
  getIt.registerLazySingleton<DatabaseService>(
    () => DatabaseService(),
  );
  
  // Repository Layer
  getIt.registerLazySingleton<UserRepository>(
    () => UserRepository(
      apiClient: getIt<ApiClient>(),
      database: getIt<DatabaseService>(),
    ),
  );
  
  // Use Case Layer
  getIt.registerLazySingleton<GetUserUseCase>(
    () => GetUserUseCase(getIt<UserRepository>()),
  );
  
  // Service Layer
  getIt.registerLazySingleton<AuthService>(
    () => AuthService(getIt<UserRepository>()),
  );
  
  getIt.registerLazySingleton<LoggerService>(
    () => LoggerService(),
  );
}

2. Использование в UI слое:

// lib/screens/user_screen.dart
class UserScreen extends StatefulWidget {
  @override
  State<UserScreen> createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  late final _userRepository = getIt<UserRepository>();
  late final _logger = getIt<LoggerService>();
  
  User? user;
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadUser();
  }

  Future<void> _loadUser() async {
    setState(() => isLoading = true);
    try {
      user = await _userRepository.getUser('123');
      _logger.info('User loaded successfully');
    } catch (e) {
      _logger.error('Failed to load user: $e');
    } finally {
      setState(() => isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User')),
      body: isLoading
          ? Center(child: CircularProgressIndicator())
          : user != null
              ? UserDetails(user: user!)
              : Center(child: Text('No user found')),
    );
  }
}

3. Архитектурная структура:

// Domain Layer (независимо от framework'ов)
abstract class UserRepository {
  Future<User> getUser(String id);
}

abstract class GetUserUseCase {
  Future<User> call(String id);
}

// Data/Infrastructure Layer
class UserRepositoryImpl implements UserRepository {
  final ApiClient apiClient;
  final DatabaseService database;

  UserRepositoryImpl({
    required this.apiClient,
    required this.database,
  });

  @override
  Future<User> getUser(String id) async {
    try {
      // Сначала пытаемся получить из кэша
      final cached = await database.getUser(id);
      if (cached != null) return cached;
      
      // Затем из API
      final user = await apiClient.getUser(id);
      await database.saveUser(user);
      return user;
    } catch (e) {
      rethrow;
    }
  }
}

// Use Case Layer
class GetUserUseCaseImpl implements GetUserUseCase {
  final UserRepository repository;

  GetUserUseCaseImpl(this.repository);

  @override
  Future<User> call(String id) => repository.getUser(id);
}

// Service Setup
void setupServiceLocator() {
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());
  getIt.registerLazySingleton<DatabaseService>(() => DatabaseService());
  
  getIt.registerLazySingleton<UserRepository>(
    () => UserRepositoryImpl(
      apiClient: getIt<ApiClient>(),
      database: getIt<DatabaseService>(),
    ),
  );
  
  getIt.registerLazySingleton<GetUserUseCase>(
    () => GetUserUseCaseImpl(getIt<UserRepository>()),
  );
}

4. Тестирование с Service Locator:

import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';

// Mock сервисы
class MockUserRepository extends Mock implements UserRepository {}
class MockLogger extends Mock implements LoggerService {}

void main() {
  group('UserScreen Tests', () {
    setUp(() {
      // Очищаем Service Locator
      getIt.reset();
      
      // Регистрируем mock'и
      final mockRepository = MockUserRepository();
      final mockLogger = MockLogger();
      
      getIt.registerSingleton<UserRepository>(mockRepository);
      getIt.registerSingleton<LoggerService>(mockLogger);
    });

    testWidgets('Should display user when loaded', (WidgetTester tester) async {
      // Arrange
      final mockRepository = getIt<MockUserRepository>();
      when(mockRepository.getUser('123'))
          .thenAnswer((_) async => User(id: '123', name: 'John'));

      // Act
      await tester.pumpWidget(MyApp());
      await tester.pumpAndSettle();

      // Assert
      expect(find.text('John'), findsOneWidget);
    });
  });
}

Типы регистрации GetIt

// 1. Singleton — один экземпляр на все приложение
getIt.registerSingleton<ApiClient>(ApiClient());

// 2. Lazy Singleton — создается при первом обращении
getIt.registerLazySingleton<AuthService>(() => AuthService());

// 3. Factory — новый экземпляр каждый раз
getIt.registerFactory<UserRepository>(() => UserRepository());

// 4. Conditional — с условиями регистрации
getIt.registerSingletonAsync<DatabaseService>(
  () async => await DatabaseService().init(),
);

// 5. С параметрами
getIt.registerFactoryParam<UserBloc, String, void>(
  (userId, _) => UserBloc(userId),
);

Плюсы и минусы

Плюсы:

  • Простая подмена для тестирования
  • Централизованное управление
  • Удобный доступ из любого места
  • Ленивая инициализация
  • Хорошо работает с архитектурой (clean, DDD)

Минусы:

  • Скрытые зависимости (не видно из сигнатуры функции)
  • Глобальное состояние (как Singleton)
  • Требует явной очистки в тестах
  • Может усложнить отладку

Альтернативы

Dependency Injection через конструктор:

class UserWidget extends StatelessWidget {
  final UserRepository userRepository;
  final LoggerService logger;

  UserWidget({
    required this.userRepository,
    required this.logger,
  });
}

Provider pattern:

final userRepositoryProvider = Provider<UserRepository>(
  (ref) => UserRepository(),
);

Итого

Service Locator (GetIt) — это мощный паттерн для управления зависимостями, который:

  • Упрощает разработку сложных приложений
  • Облегчает тестирование
  • Предоставляет глобальный доступ к сервисам
  • Хорошо интегрируется с чистой архитектурой

В современных Flutter приложениях это одна из самых популярных техник управления зависимостями, особенно в средних и больших проектах.