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


15

Il titolo dice "Dipendenza circolare", ma non è la formulazione corretta, perché per me il design sembra solido.
Tuttavia, considera il seguente scenario, in cui le parti blu sono fornite da un partner esterno e arancione è la mia implementazione. Supponi anche che ce ne sia più di uno ConcreteMain, ma voglio usarne uno specifico. (In realtà, ogni classe ha alcune dipendenze in più, ma ho provato a semplificarlo qui)

Scenario

Vorrei istanziare tutto questo con Depency Injection (Unity), ma ovviamente ottengo un StackOverflowExceptioncodice sul seguente codice, perché Runner cerca di creare un'istanza di ConcreteMain e ConcreteMain ha bisogno di un Runner.

IUnityContainer ioc = new UnityContainer();
ioc.RegisterType<IMain, ConcreteMain>()
   .RegisterType<IMainCallback, Runner>();
var runner = ioc.Resolve<Runner>();

Come posso evitarlo? C'è un modo per strutturarlo in modo che io possa usarlo con DI? Lo scenario che sto facendo ora sta impostando tutto manualmente, ma questo ConcreteMaincrea una forte dipendenza nella classe che lo crea un'istanza. Questo è ciò che sto cercando di evitare (con le registrazioni di Unity nella configurazione).

Tutto il codice sorgente di seguito (esempio molto semplificato!);

public class Program
{
    public static void Main(string[] args)
    {
        IUnityContainer ioc = new UnityContainer();
        ioc.RegisterType<IMain, ConcreteMain>()
           .RegisterType<IMainCallback, Runner>();
        var runner = 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));
    }
}

Risposte:


10

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

Quindi puoi iniettare questa Factory nel tuo costruttore Runner. Crea il Main con la fabbrica e passa la locanda stessa come parametro.

Qualsiasi altra dipendenza dal costruttore ConcreteMain può essere passata a MyMainFactory tramite IOC e trasferita 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)
    }
}

4

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


4

Alcuni contenitori IOC (ad esempio Spring o Weld) possono risolvere questo problema utilizzando proxy generati dinamicamente. I proxy vengono iniettati su entrambe le estremità e l'oggetto reale viene istanziato solo al primo utilizzo del proxy. In questo modo, le dipendenze circolari non sono un problema a meno che i due oggetti non chiamino i metodi uno sull'altro nei loro costruttori (che è facile da evitare).


4

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

Assicurati solo di non lavorare nel tuo ctor che richiede di risolvere la dipendenza Lazy.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.