Cómo manejar la "dependencia circular" en la inyección de dependencia

13

El título dice "Dependencia circular", pero no es la redacción correcta, porque para mí el diseño parece sólido.
Sin embargo, considere el siguiente escenario, donde las partes azules se dan desde un socio externo, y la naranja es mi propia implementación. También asuma que hay más de un ConcreteMain , pero quiero usar uno específico. (En realidad, cada clase tiene más dependencias, pero traté de simplificarlo aquí)

MegustaríacrearunainstanciadetodoestoconDepencyInjection(Unity),peroobviamenteobtengounStackOverflowExceptionenelsiguientecódigo,porqueRunnerintentacrearunainstanciadeConcreteMain,yConcreteMainnecesitaunRunner.

IUnityContainerioc=newUnityContainer();ioc.RegisterType<IMain,ConcreteMain>().RegisterType<IMainCallback,Runner>();varrunner=ioc.Resolve<Runner>();

¿Cómopuedoevitaresto?¿HayalgunaformadeestructurarestoparaquepuedausarloconDI?Elescenarioqueestoyhaciendoahoraesconfigurartododeformamanual,peroesoponeunafuertedependenciaenConcreteMainenlaclasequelocrea.Estoesloquetratodeevitar(conlosregistrosdeUnityenconfiguración).

Todoelcódigofuenteacontinuación(¡ejemplomuysimplificado!);

publicclassProgram{publicstaticvoidMain(string[]args){IUnityContainerioc=newUnityContainer();ioc.RegisterType<IMain,ConcreteMain>().RegisterType<IMainCallback,Runner>();varrunner=ioc.Resolve<Runner>();Console.WriteLine("invoking runner...");
        runner.DoSomethingAwesome();

        Console.ReadLine();
    }
}

public class Runner : IMainCallback
{
    private readonly IMain mainServer;

    public Runner(IMain mainServer)
    {
        this.mainServer = mainServer;
    }

    public void DoSomethingAwesome()
    {
        Console.WriteLine("trying to do something awesome");
        mainServer.DoSomething();
    }

    public void SomethingIsDone(object something)
    {
        Console.WriteLine("hey look, something is finally done.");
    }
}

public interface IMain
{
    void DoSomething();
}

public interface IMainCallback
{
    void SomethingIsDone(object something);
}

public abstract class AbstractMain : IMain
{
    protected readonly IMainCallback callback;

    protected AbstractMain(IMainCallback callback)
    {
        this.callback = callback;
    }

    public abstract void DoSomething();
}

public class ConcreteMain : AbstractMain
{
    public ConcreteMain(IMainCallback callback) : base(callback){}

    public override void DoSomething()
    {
        Console.WriteLine("starting to do something...");
        var task = Task.Factory.StartNew(() =>{ Thread.Sleep(5000);/*very long running task*/ });
        task.ContinueWith(t => callback.SomethingIsDone(true));
    }
}
    
pregunta RoelF 18.08.2014 - 11:39

4 respuestas

9

Lo que puedes hacer es crear una fábrica, MainFactory que devuelve una instancia de ConcreteMain como IMain.

Luego puedes inyectar esta Fábrica en tu constructor Runner. Crea el Main con la fábrica y pasa la posada como parámetro.

Cualquier otra dependencia del constructor ConcreteMain se puede pasar a MyMainFactory a través de IOC y se puede enviar manualmente al constructor concreto.

public class MyMainFactory
{
    MyOtherDependency _dependency;

    public MyMainFactory(MyOtherDependency dependency)
    {
        _dependency = dependency;
    }

    public IMain Create(Runner runner)
    {
        return new ConcreteMain(runner, _dependency);
    }
}

public class Runner
{
    IMain _myMain;
    public Runner(MyMainFactory factory)
    {
        _myMain = factory.Create(this)
    }
}
    
respondido por el hkon 18.08.2014 - 18:53
4

Utilice un contenedor IOC que admita este escenario. Sé que AutoFac y otros posibles lo hacen. Cuando se usa AutoFac, la restricción es que una de las dependencias debe tener PropertiesAutoWired = true y usar una propiedad para la dependencia.

    
respondido por el Esben Skov Pedersen 18.08.2014 - 11:44
4

Con Unity 3, ahora puedes inyectar Lazy<T> . Esto es similar a inyectar un caché de fábrica / objeto.

Solo asegúrese de no hacer ningún trabajo en su ctor que requiera resolver la dependencia de Lazy.

    
respondido por el dss539 02.03.2016 - 19:44
3

Algunos contenedores IOC (por ejemplo, Spring o Weld) pueden resolver este problema utilizando proxies generados dinámicamente. Los proxies se inyectan en ambos extremos y el objeto real solo se crea una instancia cuando se usa el proxy por primera vez. De esa manera, las dependencias circulares no son un problema a menos que los dos objetos se conviertan en métodos en sus constructores (lo que es fácil de evitar).

    
respondido por el vrostu 24.04.2015 - 13:23

Lea otras preguntas en las etiquetas