4

Peut-être que je mésapplique Unity, mais voilà. J'ai quelques applications, les deux chargeant les mêmes assemblys de plugin. Tous les assemblages nécessitent une bibliothèque et je veux qu'ils puissent accéder à cette bibliothèque via Unity. Cependant, pour utiliser Unity, ou tout autre framework IoC, je devrais écrire une interface pour cette bibliothèque. Je vais probablement le faire, mais comme une interface n'est pas vraiment nécessaire pour autre chose que de supporter Unity, j'ai peur que cela signifie que 1) je manque le point, ou 2) que j'applique mal le cadre. Si j'évite quelque chose qui m'offre DI, alors je devrais faire de la classe library un singleton, et ensuite le passer à tous les constructeurs de plugins, ou via une propriété publique, et je ne veux pas le faire. Cela dit, et sans réellement implémenter quoi que ce soit avec Unity, je ne reçois pas un autre détail - bien que Unity me laisse demander la bibliothèque via Resolve <>, mes plugins devront toujours avoir une référence à la Instance Unity créée dans les applications principales. Donc, est-ce un cas où votre seule option est de passer la référence Unity à tous les plugins, mais alors c'est pratique à partir de ce point, simplement parce que vous pouvez utiliser Unity pour obtenir toutes les autres dépendances?Y a-t-il quelque chose comme Unity pour des choses simples qui ne nécessitent pas d'interface?

MISE À JOUR

je réalisais que je manqué le point, mais nous espérons que quelqu'un peut me préciser - je ne devrais pas passer un arbitre à l'unité partout! Je devrais seulement créer le conteneur dans mon application, puis enregistrer tous les types par la suite. Puis, quand j'instancie tous mes plugins, ils devraient simplement pouvoir utiliser ces interfaces enregistrées, sans presque aucun effort, n'est-ce pas? Dans mon cas, mes constructeurs doivent être sans paramètre car mon plugin loader ne peut pas gérer les arguments, et dans ce cas, je vais devoir utiliser l'injection de propriété pour leur donner accès aux interfaces, n'est-ce pas?

une mise à jour

je suis allé de l'avant et essayé l'unité. J'ai enregistré l'instance de ma classe dont tous les plugins ont besoin. Je sais aussi que je finirais par rencontrer un problème avec mon chargeur de plugins car ils sont sans paramètre (et je pourrais avoir besoin de lui passer une référence à Unity pour les faire fonctionner). Cependant, juste pour l'instant je crée directement un plugin, et je le fais via la méthode Resolve. Voici donc essentiellement ce que le code ressemble à:

// app code 
ICandySettings _candy_settings = new CandySettings(); 
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>(_candy_settings); 
CandyPlugin _plugin = unity.Resolve<Candy>(); // throws null reference exception, see below. 

// plugin code 
public class Candy 
{ 
    [Dependency] 
    ICandySettings CandySettings { get; set; } 

    ... 

    public Candy() 
    { 
    CandySettings.GetSetting("box"); // CandySettings is null! why? Didn't Unity do this for me? 
    } 
} 

Donc mon problème est en ce moment que je me attends (compte tenu de ma connaissance limitée), que l'unité va mettre automagiquement la référence de CandySettings du plugin à quelque instance a été enregistré via RegisterInstance, mais ce n'est pas le cas.

UN GROUPE DE TRAVAIL OPTION

Si je saute les trucs de la fumée et des miroirs et juste passer mon UnityContainer dans le constructeur du plug-in, je peux appeler Unity.Resolve() pour définir la valeur de ma CandySettings propriété, et Tout fonctionne très bien. J'aimerais savoir pourquoi l'attribut [Dependency] ne fait pas ce que je pensais. Si je ne me trompe pas, je ne devrais pas vraiment besoin de passer Unity à chaque constructeur dans mon chargeur de plugin. Je devrais juste utiliser Unity.Resolve() et cela fonctionnerait probablement si [Dependency] fonctionne. Cependant, maintenant je comprends ce que tout le monde dit à propos de la façon dont le choix d'un conteneur IoC le forcera sur l'ensemble de votre équipe de développement.

MEF!

Jusqu'ici, MEF me gagne la bataille.C'est assez simple, et les trucs magiques de la fumée et des miroirs fonctionnent très bien pour mes besoins (actuellement). Mais j'aimerais quand même que Unity travaille. Je trouve étrange que pour MEF, je n'ai besoin que de composer les parties et tout le reste tombe en place, alors que je n'arrive pas à obtenir que Unity injecte simplement des choses automatiquement, et je dois Résoudre tout par une référence à Unity partout. Cela ne peut pas être juste.

Plus

MEF

J'aime l'idée que je peux résoudre plusieurs objets très facilement avec MEF, mais qu'en cas où j'utilise le modèle de stratégie pour dicter le comportement de code? Actuellement, c'est aussi simple que de changer la référence d'une implémentation d'un comportement à l'autre, et cela fonctionne fonctionne simplement. Est-ce que quelqu'un le fait avec MEF? Est-ce la bonne façon de le faire pour utiliser ImportMany, puis utiliser du code supplémentaire pour déterminer quel comportement dans la liste devrait être invoqué?

+1

Je pense que le principe de conception de «programme à une interface, pas une mise en œuvre» a été prouvé pour ajouter de la valeur dans de nombreux cas. Je ne dirais pas que vous mettriez en œuvre les interfaces «juste pour l'unité». –

+0

Je suis totalement d'accord, mais pour ce cas particulier, je ne vois pas comment une interface m'achète vraiment quelque chose. Là encore, j'allais dire "il n'y a aucun moyen de changer le backend d'une base de données", mais vous savez comment ça se passe ...;) – Dave

+0

@Marvin: C'est vrai, mais il a également été démontré qu'il augmentait la complexité dans de nombreux cas. Les directives de conception de bibliothèque de classes de framework .NET disent en fait de préférer les classes abstraites aux interfaces (le cas échéant) puisqu'elles sont plus compréhensibles et plus faciles à découvrir. –

Répondre

2

Si vos besoins en matière d'injection de dépendance sont mineurs (ce qui, d'après les sons de ce dernier, ils sont), vous pouvez envisager d'essayer MEF. C'est léger et facile à utiliser, et il a l'avantage d'être dans le framework directement dans .NET 4, ce qui signifie qu'il n'y a pas d'exigences supplémentaires à déployer si vous passez à .NET 4.

Entre-temps, il est supporté par 3.5 via le site Codeplex. MEF est sympa car il peut fonctionner sur n'importe quel type, pas seulement sur les interfaces. Cela étant dit, si vous voulez que les plugins utilisent une "bibliothèque" fournie par votre application, vous aurez très probablement besoin que cette bibliothèque soit disponible et référencée par vos plugins (ou au moins un ensemble de base). classes ou interfaces pour l'API).

+0

Je vais vérifier maintenant. Merci pour la suggestion, Reed. – Dave

+0

Reed, cela ressemble à une bonne façon de faire des plugins pour mon application, en fait. Actuellement, j'utilise des interfaces pour les différents types de plugins, puis mon chargeur de plugins regarde à travers les informations d'assemblage pour déterminer les interfaces supportées par chaque plugin. Le MEF semble faire la même chose, mais peut-être plus proprement. Est-ce que je le comprends correctement? – Dave

+0

@Dave: Oui. Il a été conçu spécifiquement pour étendre les applications (c'est-à-dire: les plugins), bien qu'il soit utile en tant qu'option IoC à usage général. MEF est vraiment très sympa pour la création et la composition de plugins. –

2

La plupart des conteneurs IoC peuvent mapper des classes concrètes aussi bien que des interfaces, c'est simplement que l'utilisation d'interfaces est considérée par la plupart comme une meilleure pratique en ce qui concerne la conception et les tests.

Si tout ce que vous voulez faire est de permettre le provisionnement de quelques types courants, vous pouvez faire rouler le vôtre en utilisant System.IServiceProvider. il est intégré de sorte que vous n'avez pas besoin de cascader vos dépendances IoC à votre code consommateur.

public class SomePlugin 
{ 
public SomePlugin(IServiceProvider serviceProvider) 
{ 
    _foo = serviceProvider.GetService(typeof(IFoo)) as IFoo; 
} 
} 

Ou vous pourriez avoir un localisateur de services singleton comme dans la bibliothèque Common Service Locator

public class SomePlugin 
{ 
public SomePlugin() 
{ 
    _foo = ServiceLocator.Current.GetService(typeof(IFoo)) as IFoo; 
} 
} 

EDIT: Compte tenu de vos mises à jour, je vous suggère d'utiliser BuildUp pour injecter des dépendances de propriété sur vos instances de plug-in après avoir » Nous les avons instanciées. Voir this article

PluginInstance plugin = // already loaded from whatever you're already doing 
container.BuildUp<PluginInstance>(plugin); 
+0

qui pourrait aussi faire l'affaire ... J'allais utiliser le localisateur de service avant, jusqu'à ce que je regarde la vidéo Unity et que ça sonnait comme une meilleure façon de faire. Mais dans ce cas, ce pourrait être la meilleure solution pour moi. Je vais d'abord continuer à travailler avec Unity pour comprendre comment cela fonctionne, puis je vais revenir en arrière et essayer le localisateur de service. – Dave

+0

man, System.IServiceProvider doit être l'une des interfaces les moins documentées que j'ai vues jusqu'ici. Pouvez-vous recommander les bons sites qui décrivent comment implémenter correctement IServiceProvider.GetInterface()? – Dave

+0

Hmm .. Serait-ce vraiment aussi simple que ma classe héritant de IServiceProvider, puis dans GetService(), vérifiez le Type de Passé et le comparez au Type d'objet que je veux retourner, et si cela correspond, juste le retourner? Je vais essayer cela maintenant pour voir ce qui se passe. Mais comment l'IServiceProvider dérivé est-il enregistré avec le ServiceLocator? – Dave

0

D'abord, j'ai un singleton qui contient une référence à mon instance de conteneur de l'unité, donc je ne pas passer autour de partout.

Deuxièmement, vous êtes libre d'utiliser ou unityContainer.RegisterInstance(myInstance);unityContainer.RegisterType<MyClass, MyClass>();

+0

Oui, j'utilise RegisterInstance dans mon cas. Je regarde à nouveau la vidéo Unity de MS pour voir ce que j'ai manqué la première fois. Je suis sûr qu'une fois que je serai en train de travailler, je me giflerai d'avoir pris tant de temps pour que ça marche. – Dave

0

La raison pour laquelle l'unité ne fonctionnait pas correctement dans l'exemple affiché est que dans

[Dependency] 
ICandySettings CandySettings { get; set; } 

l'accessibilité par défaut est privée. L'unité nécessite que la propriété soit publique (elle peut donc appeler le setter).

Dans la dernière version de Unity (le message d'erreur/comportement pourrait avoir été différent avec une ancienne version) en utilisant votre code posté, je reçois une exception: "La propriété CandySettings sur le type ConsoleApplication32.Candy n'est pas réglable."

Cependant, je comprends maintenant ce que tout le monde dit comment choisir un conteneur IoC forcera alors sur l'ensemble de votre équipe de développement.

Oui et non. Évidemment, les classes devront être câblées afin que les dépendances sont correctement injectées (espérons-le dans le Composition Root). Cependant, le code d'application ne fait pas (ou ne devrait pas) référencer le conteneur (par exemple en utilisant DependencyAttribute ou en passant dans IUnityContainer). Dans l'exemple affiché cela peut être fait par l'enregistrement d'un InjectionProperty pour la Candy classe:

ICandySettings _candy_settings = new CandySettings(); 
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>(_candy_settings); 
unity.RegisterType<Candy>(new InjectionProperty("CandySettings")); 
CandyPlugin _plugin = unity.Resolve<Candy>(); 

I (ainsi que beaucoup d'autres) préfèrent également injecter les dépendances dans le Constructor (injection Constructor), auquel cas l'unité fera juste la bonne chose et injecter la dépendance dans le constructeur:

public class Candy 
{ 
    public ICandySettings CandySettings { get; private set; } 

    ... 

    public Candy(ICandySettings candySettings) 
    { 
     this.CandySettings = candySettings; 
     this.CandySettings.GetSetting("box"); 
    } 
} 

le code d'enregistrement serait la même chose que dans la question initiale:

ICandySettings _candy_settings = new CandySettings(); 
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>(_candy_settings); 

var _plugin = unity.Resolve<Candy>(); // Everything is OK :) 
+0

En retard à la fête avec cette réponse, mais je voulais expliquer ce qui se passait et comment le faire fonctionner. –