From b46cfc36a2fd95da55fdd6138af5f29c852877af Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Wed, 21 Apr 2021 08:05:38 +0300 Subject: [PATCH] implemented expiremental di with new api --- example/bin/main_experiment.dart | 82 +++++++++++++++++++++++++++++ lib/experimental/binding.dart | 53 +++++++++++++++++++ lib/experimental/di.dart | 1 + lib/experimental/factory.dart | 5 ++ lib/experimental/module.dart | 18 +++++++ lib/experimental/scope.dart | 88 ++++++++++++++++++++++++++++++++ 6 files changed, 247 insertions(+) create mode 100644 example/bin/main_experiment.dart create mode 100644 lib/experimental/binding.dart create mode 100644 lib/experimental/di.dart create mode 100644 lib/experimental/factory.dart create mode 100644 lib/experimental/module.dart create mode 100644 lib/experimental/scope.dart diff --git a/example/bin/main_experiment.dart b/example/bin/main_experiment.dart new file mode 100644 index 0000000..52bc4cf --- /dev/null +++ b/example/bin/main_experiment.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'package:meta/meta.dart'; +import 'package:dart_di/experimental/scope.dart'; +import 'package:dart_di/experimental/module.dart'; + +class AppModule extends Module { + @override + void builder(Scope currentScope) { + bind().withName("apiClient").toInstance(ApiClientMock()); + bind().withName("networkRepo").toProvide( + () => NetworkDataRepository( + currentScope.resolve(named: "apiClient"), + ), + ); + // .singeltone(); + bind().toProvide( + () => DataBloc( + currentScope.resolve(named: "networkRepo"), + ), + ); + } +} + +void main() async { + final scope = openRootScope().installModules([ + AppModule(), + ]); + + final dataBloc = scope.resolve(); + dataBloc.data.listen((d) => print('Received data: $d'), + onError: (e) => print('Error: $e'), onDone: () => print('DONE')); + + await dataBloc.fetchData(); +} + +class DataBloc { + final DataRepository _dataRepository; + + Stream get data => _dataController.stream; + StreamController _dataController = new StreamController.broadcast(); + + DataBloc(this._dataRepository); + + Future fetchData() async { + try { + _dataController.sink.add(await _dataRepository.getData()); + } catch (e) { + _dataController.sink.addError(e); + } + } + + void dispose() { + _dataController.close(); + } +} + +abstract class DataRepository { + Future getData(); +} + +class NetworkDataRepository implements DataRepository { + final ApiClient _apiClient; + final _token = 'token'; + + NetworkDataRepository(this._apiClient); + + @override + Future getData() async => await _apiClient.sendRequest( + url: 'www.google.com', token: _token, requestBody: {'type': 'data'}); +} + +abstract class ApiClient { + Future sendRequest({@required String url, String token, Map requestBody}); +} + +class ApiClientMock implements ApiClient { + @override + Future sendRequest( + {@required String? url, String? token, Map? requestBody}) async { + return 'hello world'; + } +} diff --git a/lib/experimental/binding.dart b/lib/experimental/binding.dart new file mode 100644 index 0000000..9f9c590 --- /dev/null +++ b/lib/experimental/binding.dart @@ -0,0 +1,53 @@ +enum Mode { SIMPLE, INSTANCE, PROVIDER_INSTANCE } + +class Binding { + late Mode _mode; + late Type _key; + late String _name; + late T _instance; + late T Function() _provider; + late bool _isSingeltone = false; + late bool _isNamed = false; + + Binding() { + _mode = Mode.SIMPLE; + _key = T; + } + + Mode get mode => _mode; + Type get key => _key; + String get name => _name; + bool get isSingeltone => _isSingeltone; + bool get isNamed => _isNamed; + + Binding withName(String name) { + _name = name; + _isNamed = true; + return this; + } + + Binding toInstance(T value) { + _mode = Mode.INSTANCE; + _instance = value; + _isSingeltone = true; + return this; + } + + Binding toProvide(T Function() value) { + _mode = Mode.PROVIDER_INSTANCE; + _provider = value; + return this; + } + + Binding singeltone() { + if (_mode == Mode.PROVIDER_INSTANCE) { + _instance = _provider.call(); + } + _isSingeltone = true; + return this; + } + + T? get instance => _instance; + + T? get provider => _provider.call(); +} diff --git a/lib/experimental/di.dart b/lib/experimental/di.dart new file mode 100644 index 0000000..533dda5 --- /dev/null +++ b/lib/experimental/di.dart @@ -0,0 +1 @@ +class DartDi {} diff --git a/lib/experimental/factory.dart b/lib/experimental/factory.dart new file mode 100644 index 0000000..79fedd0 --- /dev/null +++ b/lib/experimental/factory.dart @@ -0,0 +1,5 @@ +import 'package:dart_di/experimental/scope.dart'; + +abstract class Factory { + T createInstance(Scope scope); +} diff --git a/lib/experimental/module.dart b/lib/experimental/module.dart new file mode 100644 index 0000000..e5e0422 --- /dev/null +++ b/lib/experimental/module.dart @@ -0,0 +1,18 @@ +import 'dart:collection'; + +import 'package:dart_di/experimental/binding.dart'; +import 'package:dart_di/experimental/scope.dart'; + +abstract class Module { + final Set _bindingSet = HashSet(); + + Binding bind() { + final binding = Binding(); + _bindingSet.add(binding); + return binding; + } + + Set get bindingSet => _bindingSet; + + void builder(Scope currentScope); +} diff --git a/lib/experimental/scope.dart b/lib/experimental/scope.dart new file mode 100644 index 0000000..fbdd37a --- /dev/null +++ b/lib/experimental/scope.dart @@ -0,0 +1,88 @@ +import 'dart:collection'; + +import 'package:dart_di/experimental/binding.dart'; +import 'package:dart_di/experimental/module.dart'; + +Scope openRootScope() => Scope(null); + +class Scope { + final Scope? _parentScope; + + Scope? get parentScope => _parentScope; + + final Map _scopeMap = HashMap(); + + Scope(this._parentScope); + + final Set _modulesList = HashSet(); + + Scope openSubScope(String name) { + final subScope = Scope(this); + if (!_scopeMap.containsKey(name)) { + _scopeMap[name] = subScope; + } + return _scopeMap[name]!; + } + + void closeSubScope(String name) { + _scopeMap.remove(name); + } + + Scope installModules(List modules) { + _modulesList.addAll(modules); + modules.forEach((module) => module.builder(this)); + return this; + } + + Scope dropModules() { + _modulesList.removeAll(_modulesList); + return this; + } + + /** + * Возвращает найденную зависимость, определенную параметром типа [T]. + * Выдает [StateError], если зависимость не может быть разрешена. + * Если вы хотите получить [null], если зависимость не может быть найдена, + * то используйте вместо этого [tryResolve] + * @return - возвращает объект типа [T] или [StateError] + */ + T resolve({String? named}) { + var resolved = tryResolve(named: named); + if (resolved != null) { + return resolved; + } else { + throw StateError( + 'Can\'t resolve dependency `$T`. Maybe you forget register it?'); + } + } + + /** + * Возвращает найденную зависимость типа [T] или null, если она не может быть найдена. + */ + T? tryResolve({String? named}) { + // 1 Поиск зависимости по всем модулям текущего скоупа + if (_modulesList.isNotEmpty) { + for (Module module in _modulesList) { + for (Binding binding in module.bindingSet) { + if (binding.key == T && + ((!binding.isNamed && named == null) || + (binding.isNamed && named == binding.name))) { + switch (binding.mode) { + case Mode.INSTANCE: + return binding.instance; + case Mode.PROVIDER_INSTANCE: + return binding.isSingeltone + ? binding.instance + : binding.provider; + default: + return null; + } + } + } + } + } + + // 2 Поиск зависимостей в родительском скоупе + return _parentScope != null ? _parentScope!.tryResolve() : null; + } +}