Як поводитися з "круговою залежністю" при ін'єкції залежності


15

У заголовку написано "Кругова залежність", але це не правильне формулювання, оскільки мені дизайн здається солідним.
Однак розглянемо наступний сценарій, коли сині частини подаються від зовнішнього партнера, а помаранчевий - це моя власна реалізація. Припустимо також, що існує більше одного ConcreteMain, але я хочу використовувати конкретний. (Насправді кожен клас має ще кілька залежностей, але я намагався спростити його тут)

Сценарій

Я хотів би придумати все це за допомогою Depency Injection (Unity), але я, очевидно, отримую StackOverflowExceptionнаступний код, тому що Runner намагається інстанціювати ConcreteMain, а ConcreteMain потребує Runner.

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

Як я можу це зробити? Чи є спосіб це структурувати, щоб я міг використовувати це з DI? Сценарій, який я зараз роблю, - це налаштувати все вручну, але це ставить важку залежність від ConcreteMainкласу, який його створює . Цього я намагаюся уникати (з конфігурацією реєстрацій Unity).

Весь вихідний код нижче (дуже спрощений приклад!);

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));
    }
}

Відповіді:


10

Що ви можете зробити, це створити фабрику MainFactory, яка повертає екземпляр ConcreteMain як IMain.

Тоді ви можете ввести цю Фабрику у свій конструктор Runner. Створіть Main з фабрики та передайте саму корчму як параметр.

Будь-які інші залежності від конструктора ConcreteMain можна передати в MyMainFactory через IOC та перенести на бетонний конструктор вручну.

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

Використовуйте контейнер IOC, який підтримує цей сценарій. Я знаю, що AutoFac та інші можливі. При використанні функції AutoFac обмеженням є те, що одна із залежностей повинна мати PropertiesAutoWired = true та використовувати властивість для залежності.


4

Деякі контейнери МОК (наприклад, Spring або Weld) можуть вирішити цю проблему за допомогою динамічно генерованих проксі-серверів. Проксі-сервери вводяться з обох кінців, а реальний об'єкт створюється лише при першому використанні проксі-сервера. Таким чином, кругові залежності не є проблемою, якщо два об'єкти не викликають один одного методами у своїх конструкторах (чого легко уникнути).


4

Завдяки Unity 3 ви можете вводити ін'єкції Lazy<T>. Це схоже на введення кеша Factory / object.

Просто будьте впевнені, що ви не працюєте у своєму ctor, що вимагає вирішення залежної залежності.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.