mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-23 21:13:35 +00:00
245 lines
8.5 KiB
Markdown
245 lines
8.5 KiB
Markdown
|
|
---
|
|||
|
|
marp: true
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<!--
|
|||
|
|
#backgroundImage: url('./doc/assets/image.png')
|
|||
|
|
backgroundSize: cover
|
|||
|
|
-->
|
|||
|
|
|
|||
|
|
# CherryPick 3.x
|
|||
|
|
### Быстро. Безопасно. Просто.
|
|||
|
|
|
|||
|
|
Современный DI-framework для Dart и Flutter
|
|||
|
|
Автор: Сергей Пеньковский
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
<!--
|
|||
|
|
backgroundImage: none
|
|||
|
|
-->
|
|||
|
|
|
|||
|
|
## Что такое CherryPick?
|
|||
|
|
|
|||
|
|
- Лёгкий и модульный framework для внедрения зависимостей (DI)
|
|||
|
|
- Фокус: производительность, безопасность и лаконичный код
|
|||
|
|
- Применяется во frontend, backend, CLI
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Эволюция: что нового в 3.x?
|
|||
|
|
|
|||
|
|
- Оптимизация скорости разрешения зависимостей
|
|||
|
|
- Интеграция с Talker для наглядного логирования DI-событий
|
|||
|
|
- Защита от циклических зависимостей на уровне ядра
|
|||
|
|
- Полностью декларативное описание DI через аннотации и генерацию кода
|
|||
|
|
- Автоматическая очистка ресурсов
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Быстро
|
|||
|
|
|
|||
|
|
* Мгновенное разрешение зависимостей
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Мгновенное разрешение зависимостей
|
|||
|
|
|
|||
|
|
- Операция resolve<T> теперь выполняется за O(1)
|
|||
|
|
- Используется Map-индексация всех биндингов в каждом скоупе (в среднем ускорение в 10x+ на крупных графах)
|
|||
|
|
- Производительность не зависит от размера приложения
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Безопасно
|
|||
|
|
|
|||
|
|
* Циклические зависимости больше не страшны
|
|||
|
|
* Интеграция с Talker и расширенное логирование
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Циклические зависимости больше не страшны
|
|||
|
|
|
|||
|
|
- CherryPick 3.x автоматически выявляет циклы при разрешении зависимостей.
|
|||
|
|
- Возможна проверка как внутри отдельного scope, так и во всём DI-графе (глобально).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### Как включить проверку циклов
|
|||
|
|
|
|||
|
|
|
|||
|
|
- Для защиты только внутри одного scope:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 1. Для текущего scope (локальная проверка)
|
|||
|
|
final scope = CherryPick.openRootScope();
|
|||
|
|
scope.enableCycleDetection();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Для защиты всей иерархии скоупов:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 2. Для всей иерархии скоупов (глобальная проверка)
|
|||
|
|
CherryPick.enableGlobalCycleDetection();
|
|||
|
|
CherryPick.enableGlobalCrossScopeCycleDetection();
|
|||
|
|
final rootScope = CherryPick.openRootScope();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### Пример обработки ошибки
|
|||
|
|
|
|||
|
|
При обнаружении цикла будет выброшено исключение с подробной трассировкой:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
try {
|
|||
|
|
scope.resolve<A>();
|
|||
|
|
} on CircularDependencyException catch(e) {
|
|||
|
|
print(e.dependencyChain);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
=== Circular Dependency Detection Example ===
|
|||
|
|
|
|||
|
|
1. Attempt to create a scope with circular dependencies:
|
|||
|
|
❌ Circular dependency detected: CircularDependencyException: Circular dependency detected for UserService
|
|||
|
|
Dependency chain: UserService -> OrderService -> UserService
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Интеграция с Talker и расширенное логирование
|
|||
|
|
|
|||
|
|
- Всё, что происходит в DI: регистрация, создание, удаление, ошибки ― теперь логируется!
|
|||
|
|
- Достаточно подключить observer:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
final talker = Talker();
|
|||
|
|
final talkerLogger = TalkerCherryPickObserver(talker);
|
|||
|
|
CherryPick.setGlobalObserver(talkerLogger);
|
|||
|
|
```
|
|||
|
|
- Логи сразу видны в консоли, UI
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|||
|
|
│ [info] | 9:41:33 89ms | [scope opened][CherryPick] scope_1757054493089_7072
|
|||
|
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|||
|
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|||
|
|
│ [verbose] | 9:41:33 90ms | [diagnostic][CherryPick] Scope created: scope_1757054493089_7072 {type: Scope, name: scope_1757054493089_7072, description: scope created}
|
|||
|
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Просто
|
|||
|
|
|
|||
|
|
* Декларативный DI
|
|||
|
|
* Автоматическая очистка ресурсов
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Декларативный DI: аннотации и генерация кода
|
|||
|
|
|
|||
|
|
- Описывайте зависимости с помощью аннотаций
|
|||
|
|
- Автоматически генерируется модуль DI и mixin для автоподстановки зависимостей
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
@module()
|
|||
|
|
abstract class AppModule extends Module {
|
|||
|
|
@provide()
|
|||
|
|
@singleton()
|
|||
|
|
Api api() => Api();
|
|||
|
|
@provide()
|
|||
|
|
Repo repo(Api api) => Repo(api);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Регистрация модуля
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
final scope = openRootScope()
|
|||
|
|
..installModules([$AppModule()]);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Field injection: минимум кода — максимум удобства
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
@injectable()
|
|||
|
|
class MyScreen extends StatelessWidget with _$MyScreen {
|
|||
|
|
@inject()
|
|||
|
|
late final Repo repo;
|
|||
|
|
|
|||
|
|
MyScreen() {
|
|||
|
|
_inject(this);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- После генерации mixin и вызова `screen._inject()` — зависимости готовы
|
|||
|
|
- Сильная типизация, никаких ручных вызовов resolve
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Автоматическая очистка ресурсов
|
|||
|
|
|
|||
|
|
Автоматическая очистка ресурсов (контроллеры, потоки, сокеты, файлы и др.).
|
|||
|
|
|
|||
|
|
Если вы регистрируете объект, реализующий Disposable, через DI-контейнер, CherryPick вызовет его метод dispose() при закрытии скоупа.
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
class MyServiceWithSocket implements Disposable {
|
|||
|
|
@override
|
|||
|
|
Future<void> dispose() async {
|
|||
|
|
await socket.close();
|
|||
|
|
print('Socket закрыт!');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
scope.installModules([
|
|||
|
|
Module((bind) => bind<MyServiceWithSocket>().toProvide(() => MyServiceWithSocket()).singleton()),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
await CherryPick.closeRootScope(); // дождётся завершения async очистки
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Почему это удобно?
|
|||
|
|
### Сравнение с ручным DI
|
|||
|
|
|
|||
|
|
|| Аннотации | Ручной DI |
|
|||
|
|
|:---|:-----------|:------------|
|
|||
|
|
|Гибко|✅|✅|
|
|||
|
|
|Кратко|✅|❌|
|
|||
|
|
|Безопасно|✅|❌ (легко ошибиться)|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## CherryPick 3.x: ваш DI-фреймворк
|
|||
|
|
|
|||
|
|
- Быстрое разрешение зависимостей
|
|||
|
|
- Гарантия безопасности и тестируемости
|
|||
|
|
- Интеграция с логированием
|
|||
|
|
- Максимально простой и декларативный код
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<!--
|
|||
|
|
#backgroundImage: url('./doc/assets/image.png')
|
|||
|
|
backgroundSize: cover
|
|||
|
|
-->
|
|||
|
|
|
|||
|
|
## Спасибо за внимание
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Вопросы?
|
|||
|
|
|
|||
|
|
- Try CherryPick - [https://pub.dev/packages/cherrypick](https://pub.dev/packages/cherrypick)
|
|||
|
|
- Contributing — [https://github.com/pese-git/cherrypick](https://github.com/pese-git/cherrypick)
|
|||
|
|
- Документация и примеры — [https://cherrypick-di.netlify.app](https://cherrypick-di.netlify.app/)
|
|||
|
|
- Готов помочь — пишите, пробуйте, внедряйте!
|
|||
|
|
|