Saltearse al contenido

Inyección de dependencias

La inyección de dependencias es un patrón de diseño que facilita la gestión de las dependencias de un objeto.

Con Reactter, la gestión de objetos se vuelve sencilla. Puedes crear, eliminar y acceder a los objetos deseados desde un único lugar centralizado, accesible desde cualquier parte de tu código, todo gracias al sólido sistema de inyección de dependencias de Reactter.

La inyección de dependencias ofrece varios beneficios. Algunos de los más destacados son:

  • Desacoplamiento: La inyección de dependencias desacopla las clases de sus dependencias, lo que facilita la modificación y la reutilización del código.
  • Inversión de control: Se adhiere al principio de inversión de control, donde la responsabilidad de la creación y gestión de objetos se delega a Reactter. Esto resulta en una mejor modularidad, reutilización y testabilidad del código.
  • Código simplificado: Al delegar la responsabilidad de crear dependencias desde las clases individuales, la inyección de dependencias simplifica el código, permitiendo que las clases se centren más en su funcionalidad principal.

API

Reactter proporciona los siguientes mecanismos de inyección de dependencias:

¿Cómo funciona?

Reactter gestiona las dependencias a través de un mecanismo centralizado que actua como un repositorio principal encargado de registrar, resolver y suministrar dependencias en toda la aplicación. Para comprender cómo funciona este sistema en su totalidad, desglosaremos el proceso en las siguientes etapas:

  1. Registro: Esta etapa implica registrar la dependencia en el contexto de Reactter utilizando un tipo específico, una función de creación (builder), un identificador (id) y un modo de dependencia (mode).

    Para realizar este registro, puedes utilizar los siguientes métodos:

    Durante el registro se emite el evento Lifecycle.registered.

  2. Resolución: Cuando se solicita una dependencia, Reactter crea una instancia de la dependencia a partir de la funcion de creación (builder) registrada, según el tipo e identificador (id) proporcionados.

    Para ello, puedes utilizar los siguientes métodos:

    Si se crea una nueva instancia de la dependencia, se emitirán los siguientes eventos:

    • Lifecycle.created
    • Lifecycle.willMount (solo en flutter_reactter)
    • Lifecycle.didMount (solo en flutter_reactter)
  3. Uso: Una vez resuelta la dependencia, su instancia puede ser utilizada en cualquier parte de la aplicación.

    Para poder acceder a la dependencia o comprobar su estado, puedes utilizar los siguientes métodos:

    Si algún estado de la dependencia se actualiza, se emitirán los siguientes eventos:

    • Lifecycle.willUpdate
    • Lifecycle.didUpdate
  4. Eliminación: En esta etapa, Reactter elimina la instancia de la dependencia según el tipo e identificador (id) proporcionados.

    Para ello, puedes utilizar los siguientes métodos:

    Durante la eliminación se emiten los siguientes eventos:

    • Lifecycle.willUnmount (solo en flutter_reactter)
    • Lifecycle.didUnmount (solo en flutter_reactter)
    • Lifecycle.deleted
  5. Desregistro: En esta etapa, Reactter elimina el registro de la dependencia del tipo y el identificador (id) proporcionados.

    Para desregistrar la dependencia, puedes utilizar los siguientes métodos:

    Cuando se elimina el registro de la dependencia, se emitirá el evento Lifecycle.unregistered.

Ejemplo

Para entenderlo mejor, retomaremos el ejemplo de la cuenta regresiva visto desde la página del gestor de estados, pero ahora utilizando la inyección de dependencias:

1
import 'package:reactter/reactter.dart';
2
import 'countdown.dart';
3
4
void main() async {
5
// Crea una instancia de la clase `Countdown`
6
final countdown = Rt.create(() => Countdown())!;
7
// Inicia la cuenta regresiva
8
await countdown.run();
9
}
1
import 'package:reactter/reactter.dart';
2
import 'counter.dart';
3
4
/// Una clase que representa una cuenta regresiva
5
class Countdown {
6
// Crea una instancia de la clase `Counter` utilizando el hook `UseDependency`
7
// con un valor inicial de 10
8
final uCounter = UseDependency.create<Counter>(() => Counter(10));
9
10
// Obtiene la instancia de `Counter`
11
Counter get counter => uCounter.instance;
12
13
/// Inicia la cuenta regresiva
14
Future<void> run() {
15
// Escucha el evento `didUpdate` de la instancia `counter`
16
// e imprime el valor actual de `count`
17
Rt.on(
18
counter,
19
Lifecycle.didUpdate,
20
(_, __) => print('Count: ${counter.count}'),
21
);
22
23
// Crea un temporizador que invoca la función `_countdown`
24
// cada segundo
25
return Timer.periodic(Duration(seconds: 1), _countdown);
26
}
27
28
// Decrementa el estado `count` en 1 cada ciclo del temporizador
29
// y elimina la instancia de `Counter` cuando el valor llega a 0
30
void _countdown(Timer timer) {
31
counter.decrement();
32
33
if (counter.count == 0) {
34
timer.cancel();
35
Rt.delete<Counter>();
36
}
37
}
38
}
1
import 'package:reactter/reactter.dart';
2
3
/// Una clase que representa un contador que contiene al estado `count`.
4
class Counter {
5
final Signal<int> _count;
6
7
int get count => _count.value;
8
9
const Counter(int initialValue) : _count = Signal(initialValue);
10
11
void decrement() => _count.value -= 1;
12
}

En este ejemplo, hemos creado una cuenta regresiva de 10 segundos, y cuando llega a 0 , la instancia de Counter es eliminada. Pero si queremos utilizar la instancia de Counter en otra parte del código, podemos hacerlo de la siguiente manera:

main.dart
1
import 'package:reactter/reactter.dart';
2
import 'countdown.dart';
3
import 'counter.dart';
4
5
void main() async {
6
// Registra la clase `Counter` con un valor inicial de 20
7
Rt.register(() => Counter(20));
4 collapsed lines
8
// Crea una instancia de la clase `Countdown`
9
final countdown = Rt.create(() => Countdown())!;
10
// Inicia la cuenta regresiva
11
await countdown.run();
12
}

Ahora, la cuenta regresiva iniciará desde 20 y cuando llegue a 0 , la instancia de Counter será eliminada. Lo que sucede es que la instancia de Counter es registrada con un valor inicial de 20 , y cuando se crea la instancia de Countdown , utiliza la instancia de Counter registrada.

Pero, ¿qué pasa si queremos utilizar la instancia de Counter en otra parte del código? Veamos:

main.dart
1
import 'package:reactter/reactter.dart';
2
import 'countdown.dart';
3
import 'counter.dart';
4
5
void main() async {
6 collapsed lines
6
// Registra la clase `Counter` con un valor inicial de 20
7
Rt.register(() => Counter(20));
8
// Crea una instancia de la clase `Countdown`
9
final countdown = Rt.create(() => Countdown())!;
10
// Inicia la cuenta regresiva
11
await countdown.run();
12
// Obtiene la instancia de la clase `Counter`
13
final counter = Rt.get<Counter>();
14
// Intenta imprimir el valor actual de `count`
15
print('Count: ${counter?.count ?? 'Counter instance not found'}');
16
}

En este caso, la cuenta regresiva funcionará como antes, pero al intentar obtener la instancia de Counter para imprimir su valor, la salida será ‘Counter instance not found’ . Esto ocurre porque Counter fue registrado como DependencyMode.builder (modo predeterminado), por lo que al ser eliminada al final de la cuenta regresiva, su registro también se elimina.

Si queremos obtener la instancia de Counter para imprimir su valor, necesitamos registrarla utilizando el modo DependencyMode.singleton, quedando de la siguiente manera:

main.dart
1
import 'package:reactter/reactter.dart';
2
import 'countdown.dart';
3
import 'counter.dart';
4
5
void main() async {
6
// Registra la clase `Counter` con un valor inicial de 20
7
Rt.register(() => Counter(20), mode: DependencyMode.singleton);
8 collapsed lines
8
// Crea una instancia de la clase `Countdown`
9
final countdown = Rt.create(() => Countdown())!;
10
// Inicia la cuenta regresiva
11
await countdown.run();
12
// Obtiene la instancia de la clase `Counter`
13
final counter = Rt.get<Counter>();
14
// Intenta imprimir el valor actual de la cuenta
15
print('Count: ${counter?.count ?? 'Counter instance not found'}');
16
}

Modos de dependencia

El mode con el que se registra una dependencia determina cómo es gestionada por Reactter. Existe tres modos de dependencia:

Builder

El modo Builder es una forma de gestionar una dependencia que registra una función de creación (builder) y crea una instancia solo si no ha sido creada previamente.

En este modo, cuando el árbol de dependencias deja de necesitarla, se elimina por completo, incluyendo el registro y la función de creación (builder).

Reactter identifica el modo builder como DependencyMode.builder y lo utiliza por defecto.

Factory

El modo Factory es una forma de gestionar una dependencia en la que se registra una función de creación (builder) y se crea una nueva instancia cada vez que se solicita.

En este modo, cuando el árbol de dependencias deja de utilizarla, la instancia se elimina, pero la función de creación (builder) permanece registrada.

Reactter identifica el modo factory como DependencyMode.factory y para activarlo, establezca el mode en el argumento de Rt.register y Rt.create , o utilice Rt.lazyFactory , Rt.factory .

Singleton

El modo Singleton es una forma de gestionar una dependencia que registra una función de creación (builder) y garantiza que la instancia se cree solo una vez.

Cuando se utiliza el modo singleton, la instancia de la dependencia y sus estados se mantienen activos, incluso si el árbol de dependencias ya no la utiliza. Esto incluye también la función de creación, a menos que se elimine explícitamente.

Reactter identifica el modo singleton como DependencyMode.singleton y para activarlo, establezca el mode en el argumento de Rt.register y Rt.create , o utilice Rt.lazySingleton , Rt.singleton .