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

Что знаешь про Dependency Injection в Dart?

2.0 Middle🔥 141 комментариев
#Dart

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

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

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

Dependency Injection в Dart и Flutter

Что такое Dependency Injection

DI — это паттерн, при котором объект получает свои зависимости извне, вместо того чтобы создавать их сам. Это позволяет сделать код более тестируемым, модульным и гибким.

Основной принцип

// ❌ Плохо — жёсткая связанность
class UserService {
  final ApiClient api = ApiClient();  // создаём сам
  final DatabaseRepository db = DatabaseRepository();
  
  Future<User> getUser(String id) async {
    return await api.fetchUser(id);
  }
}

// ✅ Хорошо — инъекция зависимостей
class UserService {
  final ApiClient api;
  final DatabaseRepository db;
  
  UserService({required this.api, required this.db});  // получаем извне
  
  Future<User> getUser(String id) async {
    return await api.fetchUser(id);
  }
}

Теперь UserService не зависит от конкретных реализаций — они передаются при создании.

DI контейнеры в Dart

1. GetIt (самый популярный)

import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setupServiceLocator() {
  // Регистрируем синглтоны
  getIt.registerSingleton<ApiClient>(ApiClient());
  getIt.registerSingleton<DatabaseRepository>(DatabaseRepository());
  
  // Регистрируем фабрики (создают новый объект каждый раз)
  getIt.registerFactory<UserService>(
    () => UserService(api: getIt<ApiClient>(), db: getIt<DatabaseRepository>())
  );
}

// Использование
class HomePage extends StatelessWidget {
  final userService = getIt<UserService>();
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: userService.getUser("123"),
      builder: (context, snapshot) => ...,
    );
  }
}

2. Riverpod (современный подход, интегрирован с Provider)

import 'package:riverpod/riverpod.dart';

// Провайдер для API клиента
final apiClientProvider = Provider((ref) => ApiClient());

// Провайдер для сервиса, зависит от apiClientProvider
final userServiceProvider = Provider((ref) {
  final api = ref.watch(apiClientProvider);
  return UserService(api: api);
});

// Провайдер для асинхронных операций
final userProvider = FutureProvider((ref) async {
  final userService = ref.watch(userServiceProvider);
  return await userService.getUser("123");
});

// Использование в виджете
class HomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    
    return userAsync.when(
      data: (user) => Text(user.name),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text("Error: $err"),
    );
  }
}

3. Provider (классический подход)

import 'package:provider/provider.dart';

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<ApiClient>(() => ApiClient()),
        ProxyProvider<ApiClient, UserService>(
          update: (context, api, _) => UserService(api: api),
        ),
      ],
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

// Использование
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userService = context.read<UserService>();
    // или
    final userService = Provider.of<UserService>(context, listen: false);
    
    return ...
  }
}

Типы регистрации зависимостей

1. Singleton — один экземпляр на всё приложение

getIt.registerSingleton<Config>(Config());

2. Lazy Singleton — создаётся при первом обращении

getIt.registerLazySingleton<Database>(() => Database());

3. Factory — новый экземпляр при каждом запросе

getIt.registerFactory<UserDTO>(() => UserDTO());

4. Lazy Factory — фабрика с ленивой инициализацией

getIt.registerLazySingleton<Logger>(() => LoggerImpl());

Преимущества DI

  • Тестируемость — легко подменить реальный объект на мок
  • Модульность — компоненты слабо связаны
  • Переиспользуемость — сервисы не привязаны к конкретным реализациям
  • Гибкость — можно менять реализации без изменения кода, который их использует

Тестирование с DI

test('UserService should fetch user from API', () async {
  // Создаём моки
  final mockApi = MockApiClient();
  final mockDb = MockDatabaseRepository();
  
  when(mockApi.fetchUser('123'))
      .thenAnswer((_) async => User(id: '123', name: 'John'));
  
  // Инъецируем моки
  final service = UserService(api: mockApi, db: mockDb);
  
  // Тестируем
  final user = await service.getUser('123');
  
  expect(user.name, 'John');
  verify(mockApi.fetchUser('123')).called(1);
});

Когда использовать какой подход

  • GetIt — для простых проектов, quick setup
  • Riverpod — для modern, reactive приложений с сложной логикой
  • Provider — компромисс между простотой и функциональностью

В современном Flutter я бы выбрал Riverpod за его мощь и типобезопасность.

Что знаешь про Dependency Injection в Dart? | PrepBro