1 votes

Castle Windsor (ou autre DI) - création d'un objet basé sur un paramètre

Je suis assez nouveau dans le domaine du DI/IoC, alors soyez indulgent avec moi...

J'ai ce genre de réglage :

interface IA
interface IB
interface IC
abstract class A : IA
class B : A, IB
class C : A, IC

interface IX
interface IY
interface IZ
abstract class X : IX
class Y : X, IY
class Z : X, IZ

Les constructeurs de B et C ressemblent à ceci :

public B(IY y);
public C(IZ z);

Maintenant, je veux que B ou C soit construit, sur la base d'une instance déjà créée de Y ou Z. Comme ceci :

IX x = new ...; // either Y or Z, determined at runtime
// lots of code
IA a = fancyfuncoftruth<IA>(x); // creates an instance of either B or C, depending on x

Une telle chose est-elle possible ?

Pour vous donner un peu de contexte : J'essaie de combiner le treeview de WPF, le modèle MVVM et DI.

Merci pour votre temps.

2voto

Mark Seemann Points 102767

Je ne suis pas sûr de comprendre ce que vous recherchez, mais il me semble que vous demandez s'il existe une fonctionnalité capable de résoudre correctement l'IA en fonction d'une valeur spécifique de IX (x).

Il est préférable de mettre en œuvre cette fonction en utilisant un Usine abstraite qui fait correspondre les instances de IX à IA.

Personnellement, j'implémenterais ceci comme une fabrique abstraite personnalisée, mais vous pouvez également utiliser UsingFactory ou UsingFactoryMethod de Castle Windsor :

IX x = new ...;

var container = new WindsorContainer();
container.AddFacility<FactorySupportFacility>();
container.Register(Component.For<IA>().UsingFactoryMethod(k =>
    {
        // Do fancy stuff with x here
        // This example just shows that x can be referenced
        // in the closure, but I'm not using it...
        if (x == null)
        {
        }
        return k.Resolve<B>();
    }));
container.Register(Component.For<B>());
container.Register(Component.For<IY>().ImplementedBy<Y>());

var result = container.Resolve<IA>();

0voto

Maximilian Csuk Points 300

Pfeh, j'ai trouvé une réponse. Probablement pas la meilleure, mais au moins quelque chose pour commencer.

Regardez l'exemple complet suivant : (j'ai utilisé NInject pour cela) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject.Core;
using Ninject.Core.Parameters;
using Ninject.Conditions;

namespace IoCTest01
{
    interface IA { }
    interface IB : IA { }
    interface IC : IA { }

    abstract class A : IA { }

    class B : A, IB
    {
        public B(IY x)
        {
            Console.WriteLine("Constructor for B called!");
        }
    }

    class C : A, IC
    {
        public C(IZ x)
        {
            Console.WriteLine("Constructor for C called!");
        }
    }

    interface IX { }
    interface IY : IX { }
    interface IZ : IX { }

    abstract class X : IX { }

    class Y : X, IY
    {
    }
    class Z : X, IZ
    {
    }

    class TestModule : StandardModule
    {
        public override void Load()
        {
            Bind<IY>().To<Y>();
            Bind<IZ>().To<Z>();

            Bind<IA>().To<B>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                e =>
                {
                    return e.Value.GetType().Equals(typeof(Y));
                }));
            Bind<IA>().To<C>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                e =>
                {
                    return e.Value.GetType().Equals(typeof(Z));
                }));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel(new TestModule());

            IX x1 = kernel.Get<IY>();
            IX x2 = kernel.Get<IZ>();

            kernel.Dispose();

            // lots of code

            kernel = new StandardKernel(new TestModule());

            var parameters = new ParameterCollection();
            parameters.Add<ConstructorArgumentParameter>(new ConstructorArgumentParameter("x", x1));
            kernel.Get<IA>(parameters);

            parameters = new ParameterCollection();
            parameters.Add<ConstructorArgumentParameter>(new ConstructorArgumentParameter("x", x2));
            kernel.Get<IA>(parameters);
        }
    }
}

Lorsqu'il est exécuté, il affiche :

Constructor for B called!
Constructor for C called!

Tout d'abord, j'ai dû injecter manuellement les objets x1 et x2 déjà instanciés dans les appels kernel.Get. Cela semblait suffisant pour permettre à NInject de résoudre la bonne entité, mais dès que j'ai ajouté un deuxième Binding pour IA, il s'est plaint des multiples bindings par défaut pour IA. J'ai donc dû faire une liaison contextuelle :

Bind<IA>().To<B>().Only(When.Context.Parameter<ConstructorArgumentParameter>("x").Matches(
                    e =>
                    {
                        return e.Value.GetType().Equals(typeof(Y));
                    }));

Ceci vérifie si le paramètre x est de type Y. Si oui, cette liaison est utilisée.

Encore une fois, bien que ce soit une solution, elle est probablement loin d'être optimale. Je souhaite que NInject puisse résoudre le bon type (B ou C) à instancier à partir du type dynamique (Y ou Z) du paramètre donné (x).

Ah bon. :-)

Quelqu'un a-t-il une meilleure solution ?

Pour Mark : le code explique-t-il mieux le problème ? La méthode Main devrait vous donner un aperçu.

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