Come gestire la "dipendenza circolare" nell'integrazione delle dipendenze

13

Il titolo dice "Dipendenza circolare", ma non è la dicitura corretta, perché per me il design sembra solido.
Tuttavia, si consideri il seguente scenario, in cui le parti blu sono fornite da un partner esterno e l'arancione è la mia implementazione. Supponiamo anche che ci sia più di un ConcreteMain , ma voglio usare uno specifico. (In realtà, ogni classe ha alcune dipendenze in più, ma ho provato a semplificarla qui)

VorreiinstallaretuttoquestoconDepencyInjection(Unity),maovviamenteottengounStackOverflowExceptionsulseguentecodice,perchéRunnertentadiistanziareConcreteMaineConcreteMainhabisognodiunRunner.

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

Comepossoevitarequesto?C'èunmodoperstrutturarequestoinmodocheiopossausarloconDI?Loscenariochestofacendoorastaimpostandotuttomanualmente,maciòponeunastrongdipendenzadaConcreteMainnellaclassecheloistanzia.Questoèquellochestocercandodievitare(conleregistrazionidiUnityinconfigurazione).

Tuttoilcodicesorgentesotto(esempiomoltosemplificato!);

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));
    }
}
    
posta RoelF 18.08.2014 - 11:39
fonte

4 risposte

9

Quello che puoi fare è creare una factory, MainFactory che restituisca un'istanza di ConcreteMain come IMain.

Quindi puoi inserire questa Fabbrica nel tuo costruttore di Runner. Crea il Main con la factory e passa la stessa loc come parametro.

Qualsiasi altra dipendenza dal costruttore ConcreteMain può essere passata a MyMainFactory tramite IOC e inviata manualmente al costruttore 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)
    }
}
    
risposta data 18.08.2014 - 18:53
fonte
4

Utilizzare un contenitore IOC che supporti questo scenario. So che AutoFac e altri possibili lo fanno. Quando si utilizza AutoFac, la restrizione è che una delle dipendenze deve avere PropertiesAutoWired = true e utilizzare una proprietà per la dipendenza.

    
risposta data 18.08.2014 - 11:44
fonte
4

Con Unity 3, ora puoi iniettare Lazy<T> . Questo è simile all'iniezione di una cache di fabbrica / oggetto.

Assicurati di non lavorare nel tuo ctor che richiede la risoluzione della dipendenza Lazy.

    
risposta data 02.03.2016 - 19:44
fonte
3

Alcuni contenitori IOC (ad esempio Spring o Weld) possono risolvere questo problema utilizzando i proxy generati dinamicamente. I proxy vengono iniettati su entrambe le estremità e l'oggetto reale viene istanziato solo quando il proxy viene utilizzato per la prima volta. In questo modo, le dipendenze circolari non sono un problema a meno che i due oggetti non chiamino metodi l'uno nei costruttori (cosa facile da evitare).

    
risposta data 24.04.2015 - 13:23
fonte