Last active
November 8, 2021 17:03
-
-
Save paulresdat/866d654174928fdf4d02462ab8c45e3d to your computer and use it in GitHub Desktop.
C# style service collection in dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum ServiceMetaDataType { | |
singleton, | |
transient, | |
} | |
class ServiceMetaData<T> { | |
late dynamic _instanceFunc; | |
T? _instance; | |
late final List<String> _dependencies; | |
late final ServiceMetaDataType _serviceType; | |
late final Map<String, ServiceMetaData> _collectionReference; | |
ServiceMetaData( | |
dynamic instanceFunc, | |
this._dependencies, | |
this._serviceType, | |
this._collectionReference) { | |
_instanceFunc = instanceFunc; | |
} | |
List<dynamic> _getDependencies() { | |
var deps = <dynamic>[]; | |
for (var dep in _dependencies) { | |
// recursively building dependencies | |
deps.add(_collectionReference[dep]!.instance); | |
} | |
return deps; | |
} | |
ServiceMetaDataType get serviceType => _serviceType; | |
T get instance { | |
if (_serviceType == ServiceMetaDataType.singleton) { | |
if (_instance == null) { | |
if (_dependencies.isNotEmpty) { | |
var deps = _getDependencies(); | |
_instance = Function.apply(_instanceFunc, deps); | |
} | |
else { | |
_instance = _instanceFunc(); | |
} | |
} | |
return _instance as T; | |
} | |
else if (_serviceType == ServiceMetaDataType.transient) { | |
if (_dependencies.isNotEmpty) { | |
return Function.apply(_instanceFunc, _getDependencies()); | |
} | |
else { | |
return _instanceFunc(); | |
} | |
} | |
throw UnimplementedError(); | |
} | |
} | |
class ServiceProvider { | |
late Map<String, ServiceMetaData> _serviceCollection; | |
ServiceProvider(this._serviceCollection); | |
T getService<T>() { | |
return _serviceCollection[T.toString()]!.instance as T; | |
} | |
} | |
class ServiceCollection { | |
ServiceCollection(); | |
final Map<String, ServiceMetaData> _services = <String, ServiceMetaData>{}; | |
void addSingleton<T>(Function registration, {List<dynamic>? injectedTypes}) { | |
if (injectedTypes != null) { | |
var dependencies = _getDeps(injectedTypes); | |
_registerDependency<T>(registration, dependencies, ServiceMetaDataType.singleton, _services); | |
} | |
else { | |
_registerDependency<T>(registration, <String>[], ServiceMetaDataType.singleton, _services); | |
} | |
} | |
List<String> _getDeps(List<dynamic> injectedTypes) { | |
var dependencies = <String>[]; | |
for (var dep in injectedTypes) { | |
dependencies.add(dep.toString()); | |
} | |
return dependencies; | |
} | |
void _registerDependency<T>(dynamic reg, List<String> deps, ServiceMetaDataType serviceType, Map<String, ServiceMetaData> _services) { | |
_services[T.toString()] = ServiceMetaData(reg, deps, serviceType, _services); | |
} | |
void addTransient<T>(Function registration, {List<dynamic>? injectedTypes}) { | |
if (injectedTypes != null) { | |
_registerDependency<T>(registration, _getDeps(injectedTypes), ServiceMetaDataType.transient, _services); | |
} | |
else { | |
_registerDependency<T>(registration, <String>[], ServiceMetaDataType.transient, _services); | |
} | |
} | |
ServiceProvider buildServiceProvider() { | |
return ServiceProvider(_services); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a C# kind of way of registering services using a service collection and service provider. | |
// This service collection works without mirrors. | |
// The primary problem with Flutter and mirrors, is that it bloats the application and has performance issues. | |
// It's not recommended to use Flutter with mirrors. This way of registering services and their dependencies | |
// has a somewhat manual approach but allows for injection into your application. It is recommended | |
// that you inject your objects from the top most layer in. Do not pass the service provider around as that | |
// is an anti-pattern. | |
abstract class IDatabase { } | |
class SqlDriver implements IDatabase { } | |
abstract class ISql { } | |
class SqlManager implements ISql { | |
final IDatabase _db; | |
SqlManager(this._db); | |
} | |
var sc = ServiceCollection(); | |
sc.addTransient<IDatabase>(() => SqlDriver()); | |
sc.addSingleton<ISql>((IDatabase db) => SqlManager(db), injectedTypes: [IDatabase]); | |
var sp = sc.buildServiceProvider(); | |
// will have the interface ISql, but be the concrete object SqlManager | |
var sql = sp.getService<ISql>(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment