37 votes

Utilisation des infrastructures d'injection de dépendance pour les classes comportant de nombreuses dépendances

J'ai été à la recherche à divers injection de dépendance de cadres de .NET que je me sens le projet que je suis en train de travailler sur serait grandement en bénéficier. Alors que je pense avoir une bonne connaissance des capacités de ces cadres, je suis encore un peu dans le flou sur la manière de mieux les introduire dans un système plus large. La plupart des démos (à juste titre) ont tendance à être assez simple les classes qui ont un ou deux dépendances.

J'ai trois questions...

D'abord, comment traitez-vous avec ceux qui sont communs mais inintéressant dépendances, par exemple, ILog, IApplicationSettings, IPermissions, IAudit. Il semble exagéré pour chaque classe d'avoir ces paramètres dans leur constructeur. Serait-il préférable d'utiliser une instance statique de la DI conteneur pour obtenir ces quand ils sont nécessaires?

MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings)
// ... versus ...
ILog log = DIContainer.Get<ILog>();

Deuxièmement, comment abordez-vous les dépendances qui pourraient être utilisés, mais peuvent être coûteux à créer. Exemple: une classe peut avoir une dépendance sur un ICDBurner interface, mais ne voulez pas la mise en œuvre concrète d'être créé à moins que les fonctionnalités de Gravure qui a été utilisé. Ne vous passez dans les interfaces d'usines (par exemple ICDBurnerFactory) dans le constructeur, ou avez-vous encore aller avec certains statique de la façon de l'obtenir directement à la DI Récipient et de le demander sur le point qu'il est nécessaire?

Troisième, supposons que vous avez une grande application Windows Forms, dans laquelle le haut niveau composant d'interface graphique (par exemple, MainForm) est le parent de potentiellement des centaines de sous-panneaux ou des formulaires modaux, qui peuvent avoir plusieurs dépendances. Est-ce à dire que MainForm devrait être mis en place pour avoir des dépendances le sur-ensemble de toutes les dépendances de ses enfants? Et si vous l'avez fait, ne serait-ce pas en fin de compte, une énorme auto-gonflant monstre que les constructions de chaque classe, il ne pourrait jamais besoin du moment où vous créez MainForm, de perdre du temps et de la mémoire dans le processus?

26voto

Nikola Malovic Points 689

Eh bien, si vous pouvez faire cela comme décrit dans les autres réponses, je crois qu'il est plus important d'être une réponse concernant votre exemple, et c'est que vous êtes probablement à la violation de PRS principe avec la classe d'avoir de nombreuses dépendances.

Ce que je considère être dans votre exemple, la rupture jusqu'à la classe en couple de plus de cohérente des classes, avec des préoccupations et donc le nombre de leurs dépendances tomber.

Nikola la loi de la SRP et de DI

"Toute classe ayant plus de 3 les dépendances devraient être mise en cause pour de PRS violation"

(Pour éviter la longue réponse, j'ai posté dans le détail de mes réponses sur le Cio et PRS blog)

6voto

Maurice Points 22343

Première: Ajouter la simple dépendances à votre constructeur en tant que de besoin. Il n'est pas nécessaire d'ajouter chaque type pour chaque constructeur, il suffit d'ajouter ceux que vous avez besoin. Besoin d'un autre, il suffit d'étendre le constructeur. La Performance ne devrait pas être une grande chose que la plupart de ces types sont susceptibles d'être singletons déjà créé après le premier appel. N'utilisez pas de statique DI Conteneur pour créer d'autres objets. Au lieu d'ajouter le Conteneur d'injection de dépendances à lui-même de sorte qu'il peut résoudre lui-même comme une dépendance. Donc quelque chose comme ceci (en supposant que l'Unité pour l'instant)

IUnityContainer container = new UnityContainer();
container.RegisterInstance<IUnityContainer>(container);

De cette façon, vous pouvez simplement ajouter une dépendance sur IUnityContainer et l'utiliser pour créer chers ou rarement les objets requis. Le principal avantage est qu'il est beaucoup plus facile lorsque les tests unitaires comme il n'y a pas statique dépendances.

Deuxième: Pas besoin de passer dans une usine de classe. En utilisant la technique ci-dessus, vous pouvez utiliser le DI conteneur lui-même pour créer des objets coûteux en cas de besoin.

Trois: Ajouter le conteneur d'injection de dépendances et de la lumière singleton dépendances pour le formulaire principal et de créer le reste par le biais de la DI conteneur en tant que de besoin. Prend un peu plus de code, mais comme vous l'avez dit, le coût de démarrage et la consommation de mémoire de la mainform serait de passer par le toit si vous créez tout au moment du démarrage.

4voto

wsorenson Points 2364

D'abord:

Vous pourrait injecter ces objets, en cas de besoin, en tant que membres au lieu de dans le constructeur. De cette façon, vous n'avez pas à apporter des modifications au constructeur que vos changements d'utilisation, et vous aussi n'avez pas besoin d'utiliser un statique.

Deuxième:

Passer dans une sorte de générateur ou de l'usine.

Troisième:

Aucune classe ne doit avoir que ces dépendances qu'elle demande elle-même. Les sous-classes doivent être injectés avec leurs propres dépendances.

4voto

Lasse V. Karlsen Points 148037

J'ai un cas similaire liées à l' "cher à créer et pourrait être utilisé", où, dans ma propre Cio mise en œuvre, je suis en ajoutant automagic de soutien pour des services usine.

En gros, au lieu de ceci:

public SomeService(ICDBurner burner)
{
}

vous voulez faire cela:

public SomeService(IServiceFactory<ICDBurner> burnerFactory)
{
}

ICDBurner burner = burnerFactory.Create();

Cela a deux avantages:

  • En coulisses, le conteneur de service qui a résolu votre service est également utilisé pour résoudre le brûleur, si et lorsque cela est demandé
  • Cela réduit les préoccupations que j'ai vu avant dans ce genre de cas où la manière typique serait d'injecter le conteneur de service lui-même en tant que paramètre à votre service, ce qui revient à dire "Ce service nécessite d'autres services, mais je ne vais pas facilement vous dire quels sont ceux qui"

L'usine de l'objet est assez facile à faire, et résout beaucoup de problèmes.

Voici mon usine de classe:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LVK.IoC.Interfaces;
using System.Diagnostics;

namespace LVK.IoC
{
    /// <summary>
    /// This class is used to implement <see cref="IServiceFactory{T}"/> for all
    /// services automatically.
    /// </summary>
    [DebuggerDisplay("AutoServiceFactory (Type={typeof(T)}, Policy={Policy})")]
    internal class AutoServiceFactory<T> : ServiceBase, IServiceFactory<T>
    {
        #region Private Fields

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly String _Policy;

        #endregion

        #region Construction & Destruction

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <param name="policy">The policy to use when resolving the service.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer, String policy)
            : base(serviceContainer)
        {
            _Policy = policy;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="serviceContainer">The service container involved.</param>
        /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception>
        public AutoServiceFactory(IServiceContainer serviceContainer)
            : this(serviceContainer, null)
        {
            // Do nothing here
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the policy that will be used when the service is resolved.
        /// </summary>
        public String Policy
        {
            get
            {
                return _Policy;
            }
        }

        #endregion

        #region IServiceFactory<T> Members

        /// <summary>
        /// Constructs a new service of the correct type and returns it.
        /// </summary>
        /// <returns>The created service.</returns>
        public IService<T> Create()
        {
            return MyServiceContainer.Resolve<T>(_Policy);
        }

        #endregion
    }
}

En gros, quand je créer le conteneur de service de mon conteneur de service générateur de classe, tous les enregistrements sont automatiquement donné un autre co-service, la mise en œuvre de IServiceFactory pour ce service, sauf si le programmeur a explicitement inscrites sur lui/elle-même pour ce service. Le service ci-dessus est ensuite utilisé, avec un paramètre spécifiant le politique (qui peut être nulle si les politiques ne sont pas utilisés).

Cela me permet de faire cela:

var builder = new ServiceContainerBuilder();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>();

using (var container = builder.Build())
{
    using (var factory = container.Resolve<IServiceFactory<ISomeService>>())
    {
        using (var service = factory.Instance.Create())
        {
            service.Instance.DoSomethingAwesomeHere();
        }
    }
}

Bien sûr, un usage plus typique serait avec votre Graveur de CD de l'objet. Dans le code ci-dessus, je voudrais résoudre le service à la place, bien sûr, mais c'est une illustration de ce qui se passe.

Donc, avec votre graveur de cd du service à la place:

var builder = new ServiceContainerBuilder();
builder.Register<ICDBurner>()
    .From.ConcreteType<CDBurner>();
builder.Register<ISomeService>()
    .From.ConcreteType<SomeService>(); // constructor used in the top of answer

using (var container = builder.Build())
{
    using (var service = container.Resolve<ISomeService>())
    {
        service.Instance.DoSomethingHere();
    }
}

à l'intérieur du service, vous pouvez maintenant avoir un service, un service de l'usine, qui sait comment faire pour résoudre votre graveur de cd de services sur demande. Ceci est utile pour les raisons suivantes:

  • Vous pourriez vouloir résoudre plus d'un service dans le même temps (de graver des deux disques en même temps?)
  • Vous ne pourriez pas besoin de cela, il pourrait être coûteux à créer, de sorte que vous ne le résoudre si nécessaire
  • Vous pourriez avoir besoin pour résoudre, d'en disposer, de les résoudre, d'en disposer, à plusieurs reprises, au lieu d'espérer/essayer de nettoyer un service existant instance
  • Vous avez également le ralentissement de votre constructeur les services dont vous avez besoin , et ceux dont vous pourriez avoir besoin

Voici deux en même temps:

using (var service1 = container.Resolve<ISomeService>())
using (var service2 = container.Resolve<ISomeService>())
{
    service1.Instance.DoSomethingHere();
    service2.Instance.DoSomethingHere();
}

Voici deux les uns après les autres, ne pas réutiliser le même service:

using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingHere();
}
using (var service = container.Resolve<ISomeService>())
{
    service.Instance.DoSomethingElseHere();
}

2voto

Peter Mounce Points 1603

D'abord:

Vous pouvez l'approche par la création d'un conteneur pour vos "inintéressant" dépendances (ILog, ICache, IApplicationSettings, etc), et l'utilisation du constructeur d'injection pour injecter, alors interne au constructeur, d'hydrater les domaines du service à partir du conteneur.Resolve() ? Je ne suis pas sûr que je serais comme ça, mais bon, c'est une possibilité.

Alternativement, vous pouvez utiliser le nouveau IServiceLocator interface commune (http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx) au lieu d'injecter les dépendances?

Deuxième:

Vous pouvez utiliser l'injection par mutateur pour l'option sur demande des dépendances? Je pense que j'irais pour l'injection d'usines et de nouveau à partir de là à la demande.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X