Привязка (Binding)
Binding — это конфигурация, которая определяет, как создавать или предоставлять конкретную зависимость. Binding поддерживает:
- Прямое присваивание экземпляра (
toInstance()
,toInstanceAsync()
) - Ленивые провайдеры (синхронные/асинхронные функции)
- Провайдеры с поддержкой динамических параметров
- Именованные экземпляры для получения по строковому ключу
- Необязательное управление жизненным циклом синглтона
Пример
void builder(Scope scope) {
// Прямое предоставление экземпляра
bind<String>().toInstance("Hello world");
// Асинхронное предоставление экземпляра
bind<String>().toInstanceAsync(Future.value("Hello world"));
// Ленивое создание синхронного экземпляра через фабрику
bind<String>().toProvide(() => "Hello world");
// Ленивое создание асинхронного экземпляра через фабрику
bind<String>().toProvideAsync(() async => "Hello async world");
// Предоставление экземпляра с динамическими параметрами (синхронно)
bind<String>().toProvideWithParams((params) => "Hello $params");
// Предоставление экземпляра с динамическими параметрами (асинхронно)
bind<String>().toProvideAsyncWithParams((params) async => "Hello $params");
// Именованный экземпляр для получения по имени
bind<String>().toProvide(() => "Hello world").withName("my_string");
// Пометить как синглтон (только один экземпляр в пределах скоупа)
bind<String>().toProvide(() => "Hello world").singleton();
}
⚠️ Важное примечание об использовании
toInstance
вbuilder
модуля:Если вы регистрируете цепочку зависимостей через
toInstance
внутриbuilder
модуля, не вызывайтеscope.resolve<T>()
для типов, которые также регистрируются в том же builder — в момент их регистрации.CherryPick инициализирует все привязки в builder последовательно. Зависимости, зарегистрированные ранее, еще не доступны для
resolve
в рамках того же выполнения builder. Попытка разрешить только что зарегистрированные типы приведет к ошибке (Can't resolve dependency ...
).Как делать правильно:
Вручную создайте полную цепочку зависимостей перед вызовомtoInstance
:void builder(Scope scope) {
final a = A();
final b = B(a);
final c = C(b);
bind<A>().toInstance(a);
bind<B>().toInstance(b);
bind<C>().toInstance(c);
}Неправильно:
void builder(Scope scope) {
bind<A>().toInstance(A());
// Ошибка! В этот момент A еще не зарегистрирован.
bind<B>().toInstance(B(scope.resolve<A>()));
}Неправильно:
void builder(Scope scope) {
bind<A>().toProvide(() => A());
// Ошибка! В этот момент A еще не зарегистрирован.
bind<B>().toInstance(B(scope.resolve<A>()));
}Примечание: Это ограничение применяется только к
toInstance
. СtoProvide
/toProvideAsync
и подобными провайдерами вы можете безопасно использоватьscope.resolve<T>()
внутри builder.
⚠️ Особое примечание относительно
.singleton()
сtoProvideWithParams()
/toProvideAsyncWithParams()
:Если вы объявляете привязку с помощью
.toProvideWithParams(...)
(или его асинхронного варианта) и затем добавляете.singleton()
, только самый первый вызовresolve<T>(params: ...)
использует свои параметры; каждый последующий вызов (независимо от параметров) вернет тот же (кешированный) экземпляр.Пример:
bind<Service>().toProvideWithParams((params) => Service(params)).singleton();
final a = scope.resolve<Service>(params: 1); // создает Service(1)
final b = scope.resolve<Service>(params: 2); // возвращает Service(1)
print(identical(a, b)); // trueИспользуйте этот паттерн только когда хотите получить "главный" синглтон. Если вы ожидаете новый экземпляр для каждого набора параметров, не используйте
.singleton()
с параметризованными провайдерами.
ℹ️ Примечание о
.singleton()
и.toInstance()
:Вызов
.singleton()
после.toInstance()
не меняет поведение привязки: объект, переданный сtoInstance()
, уже является единым, постоянным экземпляром, который всегда будет возвращаться при каждом resolve.Не обязательно использовать
.singleton()
с существующим объектом — этот вызов не имеет эффекта.
.singleton()
имеет смысл только с провайдерами (такими какtoProvide
/toProvideAsync
), чтобы гарантировать создание только одного экземпляра фабрикой.