Section courante

A propos

Section administrative du site

Conteneur de dépendances

Pourquoi un conteneur de dépendances ?

Le Fano Framework est conçu pour être extensible. Il utilise beaucoup la composition de classes. Au lieu de classes avec un héritage profond, de nombreuses classes complexes dans ce cadre d'application dépendent d'une ou plusieurs classes faisant des choses simples.

Pour cette raison, la création d'une instance d'une classe peut nécessiter la création d'autres instances de classes dont elle a besoin. Certaines classes peuvent partager la même dépendance à la même classe, d'autres non. Certaines classes ont besoin de la même instance d'une autre instance de classe, tandis que d'autres peuvent nécessiter une nouvelle instance.

Cette tâche devient de plus en plus complexe lorsque la portée du problème s'élargit. Nous devons donc fournir un moyen de gérer les dépendances entre les composantes logiciels de l'application.

Conteneur, usine et son service

Un conteneur de dépendances, ou simplement un conteneur, est une composante logiciel gérant tous les composantes logiciels (c'est-à-dire les services). Dans Fano Framework, l'implémentation du conteneur de dépendances doit implémenter l'interface IDependencyContainer.

Les services sont enregistrés dans le conteneur de dépendances lors de l'enregistrement du service, pouvant ensuite être interrogé pour récupérer l'instance de service.

Lors de l'enregistrement du service, un nom de service est associé à une classe d'usine devant implémenter l'interface IDependencyFactory étant chargée de créer le service requis.

Pour qu'un service puisse fonctionner avec l'implémentation IDependencyContainer, il doit implémenter l'interface IDependency. IDependency ne déclare aucune méthode. La déclaration suivante est donc suffisante :

  1. TMyClass=Class(TInterfacedObject,IDependency);

Le Fano Framework est livré avec la classe de base TInjectableObject implémentant l'interface IDependency.

Enregistrement de service

Pour enregistrer un service, le conteneur de dépendances fournit deux méthodes add() et factory(). Les deux méthodes attendent deux paramètres, une valeur shortstring du nom du service et une instance de IDependencyFactory responsable de la création du service. Vous pouvez utiliser n'importe quelle valeur pour la chaîne d'identification du service. Si le même identifiant est déjà enregistré, il écrasera l'IDependencyFactory précédente.

Par exemple, le code suivant enregistre la fabrique de services TSimpleRouterFactory avec le nom router. Cette classe de fabrique créera la classe TRouter :

  1. Var container:IDependencyContainer;
  2. ...
  3. container.add('router', TSimpleRouterFactory.create());

Vous pouvez également utiliser une méthode alternative :

  1. container.add(GUIDToString(IRouteMatcher), TSimpleRouterFactory.create());

Récupérer l'instance de service à partir du conteneur de dépendances

Plus tard, pour obtenir l'instance du routeur à partir du conteneur :

  1. Var inst:IDependency;
  2. ...
  3. inst := container.get('router');

Dans Fano Framework, la méthode get() de IDependencyContainer renvoie toujours une instance d'interface IDependency. Vous devez donc la convertir dans son type correct avant de pouvoir l'utiliser :

  1. Var router:IRouteMatcher;
  2. ...
  3. router:=inst as IRouteMatcher;

ou simplement :

  1. Var router:IRouteMatcher;
  2. ...
  3. router:=container.get('router') as IRouteMatcher;

ou si vous utilisez la chaîne GUID comme nom de service :

  1. Var router:IRouteMatcher;
  2. ...
  3. router:=container.get(GUIDToString(IRouteMatcher)) as IRouteMatcher;

Lorsque get() ne parvient pas à trouver le service, il génère l'exception EDependencyNotFound. Si le service est enregistré mais avec une classe d'usine nulle, l'exception EInvalidFactory est générée.

Vous pouvez également utiliser une syntaxe simplifiée de type tableau, par exemple :

  1. router:=container['router'] as IRouteMatcher;

ou :

  1. router:=container.services['router'] as IRouteMatcher;

Tester si le service est enregistré

Le conteneur de dépendances fournit la méthode has() renvoyant une valeur booléenne pouvant être utilisée pour vérifier si un service particulier est enregistré ou non :

  1. If(container.has('router'))Then Begin
  2.    // le routeur est enregistré, faites quelque chose
  3. End;

Instance unique ou multiple

Lorsqu'un service est enregistré à l'aide de la méthode add(), il est enregistré comme service à instance unique. Ainsi, chaque fois qu'un service est interrogé, le conteneur renvoie la même instance.

Dans l'exemple suivant, les routeurs 1 et 2 pointent vers la même instance :

  1. container.add('router',TSimpleRouteCollectionFactory.create());
  2. ...
  3. Var router1, router2 : IRouteMatcher;
  4. ...
  5. router1:=container.get('router') as IRouteMatcher;
  6. router2:=container.get('router') as IRouteMatcher;

Lorsqu'un service est enregistré à l'aide de la méthode factory(), il est enregistré en tant que services à instances multiples. Ainsi, chaque fois qu'un service est interrogé, le conteneur renvoie une instance différente. Dans le code ci-dessous, router1 et router2 pointeront vers des instances différentes.

  1. container.factory('router',TSimpleRouteCollectionFactory.create());
  2. ...
  3. Var router1, router2:IRouteMatcher;
  4. ...
  5. router1:=container.get('router') as IRouteMatcher;
  6. router2:=container.get('router') as IRouteMatcher;

Ajout d'un alias de service

Le conteneur de dépendances fournit la méthode alias() pour enregistrer un nouveau nom pour un service existant. Cette méthode attend deux paramètres. Le premier paramètre est le nouveau nom du service existant et le second paramètre identifie le service existant auquel attribuer un alias. Il génère une exception EDependencyAlias ??lorsque le premier et le deuxième paramètre sont identiques ou lorsque vous essayez de créer un alias pour un autre service associé à un alias.

Par exemple, si vous avez enregistré un service comme suit :

  1. container.add(GUIDToString(IRouteMatcher),TSimpleRouterFactory.create());

Vous pouvez créer un alias pour le service ci-dessus comme suit :

  1. container.alias(GUIDToString(IRouter), GUIDToString(IRouteMatcher));

Vous pouvez ensuite récupérer l'instance en utilisant :

  1. Var
  2.    router:IRouter;
  3.    routeMatcher:IRouteMatcher;
  4. ...
  5. router:=container.get(GUIDToString(IRouter)) as IRouter;
  6. routeMatcher:=container.get(GUIDToString(IRouteMatcher)) as IRouteMatcher;

Le routeur et le routeMatcher pointent tous deux vers la même instance de classe.

Cependant, si vous avez un service enregistré en tant qu'instances multiples :

  1. container.factory(GUIDToString(IRouteMatcher), TSimpleRouterFactory.create());

router et routeMatcher pointeront vers des instances différentes.

Conteneur de dépendances intégré

Le Fano Framework est livré avec un conteneur de dépendances intégré :

Bien entendu, vous êtes libre d'implémenter votre propre conteneur de dépendances, à condition qu'il implémente l'interface IDependencyContainer.

Problème de dépendance circulaire

Un problème de dépendance circulaire survient lorsqu'un service dépend de lui-même, directement ou indirectement. Par exemple, A a besoin de B, B a besoin de C, C a besoin de A.

Cela provoque une récursivité interminable. Le Fano Framework lèvera une exception ECircularDependency pour empêcher une telle condition.

L'exemple suivant montre une condition de dépendance circulaire, qui déclenchera une exception ECircularDependency :

  1. Function THomeCtrlFactory.build(Const cntr:IDependencyContainer):IDependency;Begin
  2.  result:=cntr['homeCtrl'];
  3. End;
  4. ...
  5. Container.Add('homeCtrl', THomeCtrlFactory.create());
  6. ...
  7. Router.Get('/', container['homeCtrl'] as IRequestHandler);

Si vous êtes dans une situation de dépendance circulaire, vous devez repenser l'architecture de votre application.

Injection de dépendances

L'injection des dépendance automatique n'est pas encore implémentée.



Dernière mise à jour : Vendredi, le 18 octobre 2024