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

Что такое DI?

1.2 Junior🔥 161 комментариев
#Архитектура Flutter

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

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

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

Ответ

DI (Dependency Injection) — это архитектурный паттерн, который решает проблему создания и управления зависимостями (dependencies) в приложении. Вместо того чтобы объект создавал сам нужные ему зависимости, они передаются ему извне. Это делает код более модульным, тестируемым и гибким.

Проблема, которую решает DI

// Плохо — прямая зависимость, сложно тестировать
class UserRepository {
  final database = DatabaseService(); // жёстко связано

  Future<User> getUser(String id) {
    return database.query("SELECT * FROM users WHERE id = $id");
  }
}

// Сложно написать тест — нельзя подменить DatabaseService

Решение с DI

// Хорошо — зависимость передаётся в конструктор
class UserRepository {
  final DatabaseService database;

  UserRepository(this.database);

  Future<User> getUser(String id) {
    return database.query("SELECT * FROM users WHERE id = $id");
  }
}

// Легко тестировать — передаём mock
class MockDatabase implements DatabaseService {
  @override
  Future query(String sql) async => User(id: "1", name: "John");
}

void main() {
  final mockDb = MockDatabase();
  final repo = UserRepository(mockDb);
  expect(await repo.getUser("1"), isNotNull);
}

Три способа внедрения зависимостей

1. Constructor Injection (через конструктор) — самый распространённый и предпочтительный способ:

class AuthService {
  final HttpClient httpClient;
  final StorageService storage;

  AuthService(this.httpClient, this.storage);
}

// Использование
final authService = AuthService(httpClient, storage);

2. Property Injection (через свойства) — менее предпочтительно, но иногда используется:

class AuthService {
  late HttpClient httpClient;
  late StorageService storage;
}

// Использование
final authService = AuthService();
authService.httpClient = httpClient;
authService.storage = storage;

3. Service Locator (через глобальный контейнер) — удобно, но может скрывать зависимости:

// Регистрация
final getIt = GetIt.instance;
getIt.registerSingleton<HttpClient>(HttpClient());
getIt.registerSingleton<AuthService>(AuthService());

// Использование
final authService = getIt<AuthService>();

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

GetIt — простой и популярный сервис-локатор:

import "package:get_it/get_it.dart";

final getIt = GetIt.instance;

void setupDependencies() {
  // Singleton — один экземпляр на всё приложение
  getIt.registerSingleton<DatabaseService>(DatabaseService());
  
  // Lazy singleton — создаётся только при первом использовании
  getIt.registerLazySingleton<AuthService>(
    () => AuthService(getIt<DatabaseService>()),
  );
  
  // Factory — новый экземпляр при каждом запросе
  getIt.registerFactory<UserRepository>(
    () => UserRepository(getIt<DatabaseService>()),
  );
}

// Использование
void main() {
  setupDependencies();
  runApp(MyApp());
}

class UserService {
  final repository = getIt<UserRepository>();
}

Riverpod — современный реактивный подход:

import "package:riverpod/riverpod.dart";

// Провайдеры зависимостей
final databaseProvider = Provider((ref) => DatabaseService());

final repositoryProvider = Provider((ref) {
  return UserRepository(ref.watch(databaseProvider));
});

final userServiceProvider = Provider((ref) {
  return UserService(ref.watch(repositoryProvider));
});

// Использование в виджете
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final service = ref.watch(userServiceProvider);
    return Text(service.userName);
  }
}

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

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

DI — это не опциональный паттерн, а стандарт профессиональной разработки, особенно в больших приложениях.