J'essaie de faire en sorte que Unity gère la création de mes objets et je souhaite avoir des paramètres d'initialisation qui ne sont pas connus avant l'exécution:
Pour le moment, la seule façon dont je pourrais penser à la façon de le faire est d'avoir une méthode Init sur l'interface.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Ensuite, pour l'utiliser (dans Unity), je ferais ceci:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
Dans ce scénario, le runTimeParam
paramètre est déterminé au moment de l'exécution en fonction de l'entrée utilisateur. Le cas trivial ici renvoie simplement la valeur de runTimeParam
mais en réalité le paramètre sera quelque chose comme le nom de fichier et la méthode d'initialisation fera quelque chose avec le fichier.
Cela crée un certain nombre de problèmes, à savoir que la Initialize
méthode est disponible sur l'interface et peut être appelée plusieurs fois. Définir un indicateur dans l'implémentation et lancer une exception lors d'un appel répété à Initialize
semble trop maladroit.
Au moment où je résous mon interface, je ne veux rien savoir de l'implémentation de IMyIntf
. Ce que je veux, cependant, c'est savoir que cette interface a besoin de certains paramètres d'initialisation ponctuels. Existe-t-il un moyen d'annoter (attributs?) L'interface avec ces informations et de les transmettre au framework lorsque l'objet est créé?
Edit: décrit un peu plus l'interface.
la source
runTimeParam
est une dépendance déterminée lors de l'exécution en fonction d'une entrée utilisateur. L'alternative devrait-elle être de la diviser en deux interfaces - une pour l'initialisation et une autre pour stocker les valeurs?Réponses:
N'importe quel endroit où vous avez besoin d'une valeur d'exécution pour construire une dépendance particulière, Abstract Factory est la solution.
Avoir des méthodes Initialize sur les interfaces sent une abstraction baveuse .
Dans votre cas, je dirais que vous devez modéliser l'
IMyIntf
interface sur la façon dont vous devez l'utiliser - et non sur la façon dont vous avez l'intention d'en créer des implémentations. C'est un détail de mise en œuvre.Ainsi, l'interface devrait simplement être:
Définissez maintenant la fabrique abstraite:
Vous pouvez maintenant créer une implémentation concrète de
IMyIntfFactory
qui crée des instances concrètesIMyIntf
comme celle-ci:Remarquez comment cela nous permet de protéger les invariants de la classe en utilisant le
readonly
mot - clé. Aucune méthode d'initialisation malodorante n'est nécessaire.Une
IMyIntfFactory
implémentation peut être aussi simple que ceci:Dans tous vos consommateurs pour lesquels vous avez besoin d'une
IMyIntf
instance, il vous suffit de prendre une dépendanceIMyIntfFactory
en la demandant via l' injection de constructeur .Tout conteneur DI digne de ce nom sera en mesure de câbler automatiquement une
IMyIntfFactory
instance pour vous si vous l'enregistrez correctement.la source
MyIntf
implémentation par l'usine nécessite plus derunTimeParam
(lire: d'autres services que l'on voudrait résoudre par un IoC), vous êtes toujours confronté à la résolution de ces dépendances dans votre usine. J'aime la réponse @PhilSandler consistant à transmettre ces dépendances au constructeur de l' usine pour résoudre ce problème - est-ce que c'est également votre opinion ?Habituellement, lorsque vous rencontrez cette situation, vous devez revoir votre conception et déterminer si vous mélangez vos objets avec état / données avec vos services purs. Dans la plupart des cas (pas tous), vous souhaiterez conserver ces deux types d'objets séparés.
Si vous avez besoin d'un paramètre spécifique au contexte passé dans le constructeur, une option consiste à créer une fabrique qui résout vos dépendances de service via le constructeur et prend votre paramètre d'exécution comme paramètre de la méthode Create () (ou Generate ( ), Build () ou tout ce que vous nommez vos méthodes d'usine).
Avoir des setters ou une méthode Initialize () est généralement considéré comme une mauvaise conception, car vous devez "vous rappeler" de les appeler et vous assurer qu'ils n'ouvrent pas trop l'état de votre implémentation (c'est-à-dire ce qui empêche quelqu'un de re -appel initialize ou le setter?).
la source
J'ai également rencontré cette situation à quelques reprises dans des environnements où je crée dynamiquement des objets ViewModel basés sur des objets Model (très bien décrits par cet autre article de Stackoverflow ).
J'ai aimé l' extension Ninject qui permet de créer dynamiquement des usines basées sur des interfaces:
Bind<IMyFactory>().ToFactory();
Je n'ai trouvé aucune fonctionnalité similaire directement dans Unity ; j'ai donc écrit ma propre extension pour IUnityContainer qui vous permet d'enregistrer des usines qui créeront de nouveaux objets basés sur les données d'objets existants mappant essentiellement d'une hiérarchie de types à une hiérarchie de types différente: UnityMappingFactory @ GitHub
Dans un but de simplicité et de lisibilité, je me suis retrouvé avec une extension qui vous permet de spécifier directement les mappages sans déclarer des classes d'usine ou des interfaces individuelles (un gain de temps réel). Vous ajoutez simplement les mappages là où vous enregistrez les classes pendant le processus de démarrage normal ...
Ensuite, vous déclarez simplement l'interface de fabrique de mappage dans le constructeur pour CI et utilisez sa méthode Create () ...
En prime, toutes les dépendances supplémentaires dans le constructeur des classes mappées seront également résolues lors de la création de l'objet.
Évidemment, cela ne résoudra pas tous les problèmes, mais cela m'a plutôt bien servi jusqu'à présent, alors j'ai pensé que je devrais le partager. Il y a plus de documentation sur le site du projet sur GitHub.
la source
Je ne peux pas répondre avec une terminologie spécifique à Unity, mais il semble que vous soyez en train d'apprendre l'injection de dépendances. Si tel est le cas, je vous exhorte à lire le guide de l'utilisateur bref, clair et riche en informations de Ninject .
Cela vous guidera à travers les différentes options que vous avez lors de l'utilisation de DI et comment prendre en compte les problèmes spécifiques auxquels vous serez confronté en cours de route. Dans votre cas, vous souhaiterez probablement utiliser le conteneur DI pour instancier vos objets, et faire en sorte que cet objet obtienne une référence à chacune de ses dépendances via le constructeur.
La procédure pas à pas détaille également comment annoter des méthodes, des propriétés et même des paramètres à l'aide d'attributs pour les distinguer au moment de l'exécution.
Même si vous n'utilisez pas Ninject, la procédure pas à pas vous donnera les concepts et la terminologie de la fonctionnalité qui convient à votre objectif, et vous devriez être en mesure de mapper ces connaissances à Unity ou à d'autres frameworks DI (ou vous convaincre d'essayer Ninject) .
la source
Je pense que je l'ai résolu et que c'est plutôt sain, donc ça doit être à moitié correct :))
Je me suis divisé
IMyIntf
en une interface "getter" et "setter". Alors:Puis la mise en œuvre:
IMyIntfSetter.Initialize()
peut toujours être appelé plusieurs fois mais en utilisant des bits de paradigme du localisateur service, nous pouvons le résumer assez bien pour qu'il s'agisseIMyIntfSetter
presque d'une interface interne distincte deIMyIntf
.la source