Skip to content

Commit

Permalink
Merge pull request #10 from zooper-lib/feature/annotations-rename
Browse files Browse the repository at this point in the history
Feature/annotations rename
  • Loading branch information
DirtyNative authored Mar 29, 2024
2 parents d9b1540 + c9ed00e commit c304f86
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 40 deletions.
85 changes: 50 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class MyDIAdapter extends ServiceLocatorAdapter {
}
```

## Definitions

- **Singleton**: You will register an instance of a type which is then stored in memory. This instance is then reused.
- **Transient**: You will register a factory which will be called every time you resolve the type. This way you will always get a new instance.
- **Lazy**: You will register a factory which will be called the first time you resolve the type and then the instance will be stored in memory. This way you will always get the same instance.

## Initialization and Usage

With your custom adapter in place, initialize the ServiceLocator like so:
Expand All @@ -37,14 +43,14 @@ void main() {
}
```

### Instances
### Singletons

#### Simple registration

To register an instance you directly pass an instance of the object you want to have registered:
To register a `Singleton` you directly pass an instance of the object you want to have registered:

```dart
ServiceLocator.I.registerInstance(MyService());
ServiceLocator.I.registerSingleton(MyService());
```

And to resolve that instance you call the `resolve()` method:
Expand All @@ -68,13 +74,26 @@ The same principle applies to the following registration option

---

#### Registering with a factory

You can also register a `Singleton` with a factory:

```dart
ServiceLocator.I.registerSingletonFactory<MyService>(
(serviceLocator, namedArgs) => MyService(),
);
```

This way you have more control over the instance creation.
Note that the factory will only be called once, and directly after the registration.

#### Named registration

You can pass a name with your registration.

```dart
ServiceLocator.I.registerInstance(MyService(), name: 'One');
ServiceLocator.I.registerInstance(MyService(), name: 'Two');
ServiceLocator.I.registerSingleton(MyService(), name: 'One');
ServiceLocator.I.registerSingleton(MyService(), name: 'Two');
```

This way you can resolve different instances with ease:
Expand All @@ -92,50 +111,48 @@ The same principle as named registrations, but with a different property

The same principle as named registrations, but with a different property. Mostly used to define your instances under different environments like "dev", "test", "prod", ...

### Factory registration
### Transient registration

#### Simple registration

The difference here to the instance registration is, that you provide a function which tells the framework how to create the instance.

```dart
ServiceLocator.I.registerFactory<MyService>(
ServiceLocator.I.registerTransient<MyService>(
(serviceLocator, namedArgs) => MyService(),
);
```

And to resolve, you do the same as with the instance resolving:
And to resolve, you do the same as with the `Singleton` resolution:

```dart
final MyService myService = ServiceLocator.I.resolve<MyService>();
```

So every time we call `resolve()` on a as factory registered type we will create a new instance of this class.
So every time we call `resolve()` on a as `Transient` registered type we will create a new instance of this class.

---

**NOTE**

The difference to `registerInstance()` is, that with a factory you construct the object at runtime.
The difference to `registerSingleton()` is, that with a `Transient` you construct the object at runtime.

---

#### Resolving with parameters

One feature for factories is, that you can pass arguments to resolve the instance.
One feature for `Transient` factories is, that you can pass arguments to resolve the instance.
First you need to tell the framework how to resolve the factory:

```dart
ServiceLocator.I.registerFactory<UserService>(
(ServiceLocator locator, Map<String, dynamic> namedArgs) => UserService(
id: namedArgs['theUserId'] as UserId, // This is how your parameter will be provided
username: namedArgs['theUsername'] as String, // This is how your parameter will be provided
password: namedArgs['thePassword'] as String, // This is how your parameter will be provided
),
);
ServiceLocator.I.registerTransient<UserService>(
(ServiceLocator locator, Map<String, dynamic> namedArgs) => UserService(
id: namedArgs['theUserId'] as UserId, // This is how your parameter will be provided
username: namedArgs['theUsername'] as String, // This is how your parameter will be provided
password: namedArgs['thePassword'] as String, // This is how your parameter will be provided
),
);
```

As you can see the Function to register a factory has two parameters:
As you can see the function to register a factory has two parameters:

- ServiceLocator locator
- Map<String, dynamic> namedArgs
Expand All @@ -145,30 +162,28 @@ The namedArgs is a Map of arguments you will pass when trying to resolve a facto

```dart
final UserService userService =
ServiceLocator.I.resolve<UserService>(
namedArgs: {
'theUserId': UserId('1'),
'theUsername': 'HansZimmer123',
'thePassword': 'blafoo1!',
},
);
ServiceLocator.I.resolve<UserService>(
namedArgs: {
'theUserId': UserId('1'),
'theUsername': 'HansZimmer123',
'thePassword': 'blafoo1!',
},
);
```

If you don't pass a required named argument, a `TypeError` will be thrown.

### Lazy Registration

In addition to the existing instance and factory registration capabilities, this package now supports lazy registration of services. Lazy registration allows you to defer the creation of an object until it is first needed, which can improve the startup time of your application and reduce initial memory usage.
In addition to the existing `Singleton` and `Transient` registration capabilities, this package now supports `Lazy` registration of services. `Lazy` registration allows you to defer the creation of an object until it is first needed, which can improve the startup time of your application and reduce initial memory usage.

```dart
ServiceLocator.I.registerLazy<MyLazyService>(
Lazy<MyLazyService>(() => MyLazyService()),
);
```

With lazy registration, the `MyLazyService` instance will not be created at the time of registration but will be instantiated upon the first call to resolve.

To resolve a lazy registered service, you use the same resolve method:
To resolve a `Lazy` registered service, you use the same resolve method:

```dart
final MyLazyService myLazyService = ServiceLocator.I.resolve<MyLazyService>();
Expand All @@ -178,9 +193,9 @@ The first call to resolve for a lazy registered service will instantiate the ser

#### Advantages of Lazy Registration

- Improved Startup Performance: By deferring the instantiation of services until they are actually needed, you can reduce the workload during application startup, leading to faster launch times.
- Optimized Resource Usage: Lazy registration helps in minimizing the memory footprint at startup by only creating service instances when they are required.
- Flexibility: This feature adds an extra layer of flexibility in managing service lifecycles, allowing for a more dynamic and responsive application structure.
- **Improved Startup Performance**: By deferring the instantiation of services until they are actually needed, you can reduce the workload during application startup, leading to faster launch times.
- **Optimized Resource Usage**: Lazy registration helps in minimizing the memory footprint at startup by only creating service instances when they are required.
- **Flexibility**: This feature adds an extra layer of flexibility in managing service lifecycles, allowing for a more dynamic and responsive application structure.

## Code generation

Expand Down
24 changes: 22 additions & 2 deletions lib/src/concrete_service_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ConcreteServiceLocator implements ServiceLocator {
_config = config ?? const ServiceLocatorConfig();

@override
void registerInstance<T extends Object>(
void registerSingleton<T extends Object>(
T instance, {
Set<Type>? interfaces,
String? name,
Expand Down Expand Up @@ -50,7 +50,27 @@ class ConcreteServiceLocator implements ServiceLocator {
}

@override
void registerFactory<T extends Object>(
void registerSingletonFactory<T extends Object>(
T Function(ServiceLocator serviceLocator) factory, {
Set<Type>? interfaces,
String? name,
dynamic key,
String? environment,
}) {
// Resolve the Singleton
final instance = factory(ServiceLocator.I);

return registerSingleton(
instance,
interfaces: interfaces,
name: name,
key: key,
environment: environment,
);
}

@override
void registerTransient<T extends Object>(
T Function(
ServiceLocator serviceLocator,
Map<String, dynamic> namedArgs,
Expand Down
39 changes: 36 additions & 3 deletions lib/src/service_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,48 @@ abstract class ServiceLocator {
/// locator's registry.
/// Further errors might be thrown based on the specific behavior or constraints of the underlying service
/// locator adapter, particularly when internal validation or consistency checks fail.
void registerInstance<T extends Object>(
void registerSingleton<T extends Object>(
T instance, {
Set<Type>? interfaces,
String? name,
dynamic key,
String? environment,
});

/// Registers a factory method with the service locator for dynamic instance creation.
/// Registers a resolution strategy for a singleton of type [T] using a factory function, enabling custom instantiation logic.
///
/// This method allows the consumer to specify exactly how the singleton instance of [T] should be created, offering
/// greater control over the instantiation process. The factory function is invoked directly after calling this function.
///
/// - [factory]: A function that takes a [ServiceLocator] as its argument and returns an instance of type [T].
/// This function encapsulates the custom logic for creating the singleton instance, leveraging the [ServiceLocator]
/// for any necessary dependency resolution. This approach facilitates complex initialization scenarios where
/// the instantiation of [T] might depend on other services or configurations managed by the service locator.
/// - [interfaces]: An optional `Set` of types that the singleton is expected to implement. This allows the singleton
/// to be resolved by these interface types as well, promoting a design that favors abstraction over concrete
/// implementations.
/// - [name]: An optional identifier to distinguish between multiple factory registrations for the same type or
/// interface within the locator. Useful when the application requires different variations of the same service
/// type, identifiable by name.
/// - [key]: An optional discriminator that provides additional granularity in the resolution process, complementing
/// the type and name to facilitate more specific resolution conditions.
/// - [environment]: An optional tag to restrict the availability of the registered factory to certain runtime
/// environments or configurations, aligning the service availability with the application's operational context.
///
/// Throws [StateError] if a registration conflict occurs, safeguarding against unintended duplication or override
/// of factory registrations based on a combination of type, interfaces, name, key, and environment. The integrity
/// of the service locator's registry is thus preserved, ensuring consistent and predictable behavior. Additional
/// validation or constraint-related errors might be thrown by the service locator's implementation, reflecting
/// specific requirements or conditions enforced by the underlying mechanism.
void registerSingletonFactory<T extends Object>(
T Function(ServiceLocator serviceLocator) factory, {
Set<Type>? interfaces,
String? name,
dynamic key,
String? environment,
});

/// Registers a factory method with the service locator for transient instance creation.
///
/// The factory method provides a way to instantiate objects of type [T] on demand, offering
/// more control over the creation process. This is particularly useful for instances that
Expand All @@ -103,7 +136,7 @@ abstract class ServiceLocator {
/// consistency of registrations based on type, interfaces, name, key, and environment.
/// Additional errors may be encountered based on the service locator's underlying implementation,
/// particularly if internal validation fails or if there are issues during the resolution process.
void registerFactory<T extends Object>(
void registerTransient<T extends Object>(
T Function(
ServiceLocator serviceLocator,
Map<String, dynamic> namedArgs,
Expand Down

0 comments on commit c304f86

Please sign in to comment.