Comment puis-je éviter le modèle de localisateur de service? Devrais-je?

Je travaille actuellement sur un système WinForms (je sais) où il y a beaucoup d’injecteurs de Constructor Injection lors de la création de formulaires, mais si ces formulaires / vues doivent ouvrir un autre formulaire, je trouve que le conteneur DI a également été injecté afin que nous puissions localiser l’implémentation de l’interface d’affichage souhaitée au moment de l’exécution. par exemple

 public partial class MyView : Form, IMyView { private readonly IDIContainer _container; public MyView(IDIContainer container) { InitializeComponent(); _container = container; } public OpenDialogClick(object sender, EventArgs e) { var dialog = container.Resolve(); dialog.ShowDialog(this); } } 

Je suis conscient qu’il s’agit essentiellement d’utiliser le conteneur en tant que localisateur de services. On m’a répété à maintes resockets que c’était considéré comme un anti-modèle, alors j’aimerais éviter cet usage.

Je pourrais probablement insérer la vue dans le constructeur comme ceci:

 public partial class MyView : Form, IMyView { private readonly IDialogView _dialog; public MyView(IDialogView dialog) { InitializeComponent(); _dialog = dialog; } public OpenDialogClick(object sender, EventArgs e) { dialog.ShowDialog(this); } } 

Mais que se passe-t-il si la vue de dialog est assez coûteuse à instancier?

Il a été suggéré de créer une sorte d’usine de formulaires utilisant en interne le conteneur DI, mais pour moi, cela me semble tout simplement créer une enveloppe autour d’un autre localisateur de service.

Je sais qu’à un moment donné, quelque chose doit savoir comment créer un IDialogView, je pense donc que le problème est résolu lors de la création de la racine composite (probablement pas idéal s’il existe de nombreuses formes et qu’il en coûte cher de créer). , ou la racine composite elle-même a un moyen de résoudre la dépendance. Dans quel cas la racine composite doit avoir une dépendance de type localisateur de service? Mais alors comment les formulaires enfants créeraient-ils des dialogs comme celui-ci? Appelleraient-ils le composite via, par exemple, des événements, pour ouvrir des dialogs comme celui-ci?

Un problème particulier que je ne cesse de rencontrer est que le conteneur est presque impossible de se moquer facilement. C’est en partie ce qui me fait penser à l’idée de l’usine de formulaires, même s’il s’agirait simplement d’une enveloppe autour du conteneur. Est-ce une raison raisonnable?

Est-ce que je me suis cru dans un nœud? Y at-il un moyen simple à travers cela? Ou dois-je simplement couper le nœud et trouver quelque chose qui fonctionne pour moi?

Ou dois-je simplement couper le nœud et trouver quelque chose qui fonctionne pour moi?

Classe d’usine:

 interface publique IDialogFactory {
     IDialogView CreateNew ();
 }

 // La mise en oeuvre
 classe scellée DialogFactory: IDialogFactory {
    public IDialogView CreateNew () {
       retourne un nouveau DialogImpl ();
    }
 }

 // ou singleton ...
 classe scellée SingleDialogFactory: IDialogFactory {
    dialog privé IDialogView;
    public IDialogView CreateNew () {
       si (dialog == null) {
          dialog = new DialogImpl ();
       }
       retourner le dialog;
    }
 }

Votre code:

 classe partielle publique MyView: Form, IMyView {
    usine IDialogFactory privée en lecture seule;
    MyView public (usine IDialogFactory) {
       InitializeComponent ();
       // assert (factory! = null);
       this.factory = usine;
    }

    OpenDialogClick public (expéditeur d'object, EventArgs e) {
       using (var dialog = this.factory.CreateNew ()) {
          dialog.ShowDialog (this);
       }
    }
 }

Enregistrement avec SimpleInjector

 container.RegisterSingle(); 

ou en utilisant singleton version

 container.RegisterSingle(); container.RegisterSingle(); 

Une usine locale, satisfaite d’une implémentation utilisant le conteneur et configurée dans la racine de la composition, n’est pas un localisateur de service, mais un résolveur de dépendances .

La différence est la suivante: le localisateur est défini et satisfait quelque part au voisinage de la définition du conteneur. Dans un projet distinct, pour utiliser le localisateur, vous avez besoin d’une référence externe à l’infrastructure de conteneur. Cela fait que le projet s’appuie sur une dépendance externe (le localisateur).

D’autre part, le résolveur de dépendance est local dans le projet. Il est utilisé pour satisfaire les dépendances dans son voisinage proche mais il ne dépend de rien d’extérieur.

La racine de la composition ne doit pas être utilisée pour résoudre des dépendances spécifiques telles que celle qui vous préoccupe. Au lieu de cela, la racine de la composition doit configurer les implémentations de tous ces résolveurs de dépendance locaux utilisés dans l’application. Plus le résolveur est local, mieux c’est. L’usine constructeur du MVC est un bon exemple. D’autre part, le résolveur de WebAPI gère assez peu de services différents et rest un bon résolveur – il est local dans l’infrastructure webapi, il ne dépend de rien (plutôt – les autres services webapi en dépendent) et il peut être mis en œuvre de toutes les manières possibles et mis en place dans la racine de la composition.

Il y a quelque temps j’ai écrit une entrée de blog à ce sujet

http://www.wiktorzychla.com/2012/12/di-factories-and-composition-root.html

Vous y trouverez votre problème discuté et un exemple de la manière dont vous avez configuré une usine, également appelée résolveur.

Vous ne voulez certainement pas faire passer votre conteneur DI autour de votre application. Votre boîte DI ne doit faire partie que de votre racine de composition. Vous pouvez toutefois avoir une usine qui utilise le conteneur DI dans la racine de composition. Donc, si Program.cs est l’endroit où vous connectez tout, vous pouvez simplement définir cette classe d’usine.

WinForms n’a pas été conçu avec l’ID en tête; les formulaires sont générés et ont donc besoin de constructeurs par défaut. Cela peut ou peut ne pas être un problème selon le conteneur DI que vous utilisez.

Je pense que dans ce cas, la douleur liée à l’utilisation d’une injection de constructeur dans WinForm est supérieure à celle des pièges que vous pourriez rencontrer lors de l’utilisation d’un service de localisation. Il n’y a pas de honte à déclarer une méthode statique dans votre racine de composition (Program.cs) qui encapsule un appel dans votre conteneur DI pour résoudre vos références.

Je connais très bien ce problème. Tout ce que j’ai appris sur la solution à ce problème (et j’en ai APPRIS BEAUCOUP) correspond plus ou moins au localisateur de services.