Ответ
Dependency Injection (DI) — это паттерн, который я активно использую для создания тестируемого и слабосвязанного кода. Вместо того чтобы класс сам создавал свои зависимости, он получает их извне.
Основные подходы в Dart/Flutter:
-
Constructor Injection (наиболее частый в моём коде): Зависимости передаются через конструктор.
class AuthService { final ApiClient _client; final StorageRepository _storage; AuthService(this._client, this._storage); // Зависимости инжектятся Future<void> login() async { final token = await _client.post('/login'); await _storage.saveToken(token); } } -
Использование пакетов для DI: Для больших проектов я использую
get_itв связке сinjectableдля автоматической генерации регистрации.// Настройка locator (get_it) final getIt = GetIt.instance; @injectable class ApiClient { ... } @injectable class AuthService { AuthService(this.apiClient); final ApiClient apiClient; } // Где-то в коде: final authService = getIt<AuthService>(); // Все зависимости разрешены автоматически
Преимущества, которые я получил на практике:
- Тестируемость: Легко подменить
ApiClientмоком в unit-тестах. - Гибкость: Замена реализации (например,
FirebaseStorageRepositoryнаHiveStorageRepository) происходит в одном месте — контейнере DI. - Чистая архитектура: Классы перестают знать о способе создания своих зависимостей.
Во Flutter DI часто реализуется через виджеты провайдеры (Provider, Riverpod), которые по сути являются контейнерами зависимостей для поддерева виджетов.
Ответ 18+ 🔞
Давай разберём эту тему про внедрение зависимостей, а то некоторые её так преподносят, будто это какая-то магия космического уровня, а на деле всё просто, как три копейки.
Смотри, Dependency Injection — это, по сути, просто способ не быть распиздяем. Вместо того чтобы твой класс сам внутри себя как угорелый создавал всё, что ему нужно (типа final client = ApiClient()), он говорит: «Эй, дайте мне это сюда, я работать буду». И ему это дают извне. Всё. Никакой ебушки-воробушки.
Как это обычно выглядит в Dart/Flutter:
-
Внедрение через конструктор (Constructor Injection). Самый частый и нормальный способ. Выглядит честно и прямолинейно.
class AuthService { final ApiClient _client; // Зависимость №1 final StorageRepository _storage; // Зависимость №2 // Сюда, сука, принесите мне то, что я прошу. Сам я ничего искать не буду. AuthService(this._client, this._storage); Future<void> login() async { final token = await _client.post('/login'); await _storage.saveToken(token); } }Прелесть в чём? А в том, что теперь
AuthService— хитрая жопа. Он не парится, откуда взялисьApiClientиStorageRepository. Ему дали — он и работает. А в тестах ты ему вместо настоящего клиента суёшь заглушку, и он даже не заметит подмены. Доверия к нему, блядь, ноль, но это и хорошо. -
Использование пакетов. Когда проект растёт, вручную всё это собирать становится заебище. Тут на помощь приходят
get_itиinjectable. Это как большой шкаф, где всё разложено по полочкам.// Настраиваем эту самую полку (locator) final getIt = GetIt.instance; @injectable // Говорим системе: "Эй, этот класс можешь создавать сам" class ApiClient { ... } @injectable class AuthService { AuthService(this.apiClient); // Система видит, что нужен ApiClient, идёт на полку, берёт его и суёт сюда final ApiClient apiClient; } // А где-то в коде ты просто берёшь готовый сервис: final authService = getIt<AuthService>(); // Все зависимости уже внутри, как матрёшка!Сначала кажется, что это овердохуища магии, но потом привыкаешь. Главное — правильно полки настроить.
А нахуя это всё, спросишь?
- Тестируемость — пизда рулю. Это главный козырь. Хочешь потестить
AuthService? Подсовываешь ему мокApiClient, который не лезет в сеть, а тупо возвращает фейковый токен. И всё. Никаких танцев с бубном. - Гибкость. Решил, что
HiveStorageRepository— говно, и хочешь перейти наIsar? Идешь в одно-единственное место (в тот самый «шкаф» или провайдер) и меняешь там настройку. А не бегаешь по всему коду, выискивая, где ты этот репозиторий создавал. Экономия нервов — просто волнение, ебать. - Чистота. Классы перестают быть всезнайками. Они знают только свою работу, а не то, как рождаются их помощники. Это и есть слабая связность. Красота.
Во Flutter, кстати, виджеты-провайдеры (Provider, Riverpod) — это по своей сути те же контейнеры для зависимостей, только заточенные под виджетное дерево. Кинул провайдер сверху — и все виджеты ниже могут достучаться до нужного объекта. Удобно, ёпта.
В общем, суть не в том, чтобы выучить кучу терминов, а в том, чтобы перестать создавать зависимости внутри класса, как последний маньяк. Отдай эту работу наружу — и жить станет проще. Сам от этого подхода охуел, когда осознал, насколько всё логичнее становится.