Jak obsługiwać „zależność cykliczną” we wstrzykiwaniu zależności

15

Tytuł mówi „Zależność kołowa”, ale nie jest to właściwe sformułowanie, ponieważ dla mnie konstrukcja wydaje się solidna.
Zastanów się jednak nad następującym scenariuszem, w którym niebieskie części podano od partnera zewnętrznego, a pomarańczowy to moja własna implementacja. Załóżmy również, że jest więcej niż jeden ConcreteMain, ale chcę użyć określonego. (W rzeczywistości każda klasa ma kilka innych zależności, ale próbowałem to uprościć tutaj)

Scenariusz

Chciałbym zaimplementować to wszystko za pomocą Deprence Injection (Unity), ale oczywiście otrzymuję StackOverflowExceptionnastępujący kod, ponieważ Runner próbuje utworzyć instancję ConcreteMain, a ConcreteMain potrzebuje Runnera.

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

Jak mogę tego uniknąć? Czy jest jakiś sposób, aby to ustrukturyzować, aby móc używać go z DI? Scenariusz, który teraz tworzę, polega na ręcznym konfigurowaniu wszystkiego, ale nakłada to na ConcreteMainklasę silną zależność, która go tworzy . Właśnie tego próbuję uniknąć (z rejestracjami Unity w konfiguracji).

Cały kod źródłowy poniżej (bardzo uproszczony przykład!);

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));
    }
}
RoelF
źródło

Odpowiedzi:

10

Możesz stworzyć fabrykę MainFactory, która zwraca instancję ConcreteMain jako IMain.

Następnie możesz wstrzyknąć tę Fabrykę do konstruktora Runner. Utwórz Main z fabryką i przekaż samą karczmę jako parametr.

Wszelkie inne zależności od konstruktora ConcreteMain można przekazać do MyMainFactory za pośrednictwem IOC i ręcznie przekazać do konstruktora betonu.

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)
    }
}
hkon
źródło
4

Użyj kontenera IOC, który obsługuje ten scenariusz. Wiem, że AutoFac i możliwe inne. Podczas korzystania z funkcji AutoFac ograniczenie polega na tym, że jedna z zależności musi mieć właściwość PropertiesAutoWired = true i użyć właściwości zależności.

Esben Skov Pedersen
źródło
4

Niektóre kontenery IOC (na przykład Spring lub Weld) mogą rozwiązać ten problem za pomocą dynamicznie generowanych serwerów proxy. Serwery proxy są wstrzykiwane na oba końce, a rzeczywisty obiekt jest tworzony w momencie tworzenia serwera proxy. W ten sposób zależności cykliczne nie stanowią problemu, chyba że oba obiekty wywołują metody w swoich konstruktorach (co jest łatwe do uniknięcia).

vrostu
źródło
4

Dzięki Unity 3 możesz teraz wstrzykiwać Lazy<T>. Jest to podobne do wstrzykiwania pamięci podręcznej fabryki / obiektu.

Tylko upewnij się, że nie pracujesz w swoim ctor, który wymaga rozwiązania zależności Lazy.

dss539
źródło