Cara menangani "ketergantungan melingkar" dalam injeksi ketergantungan

15

Judulnya bertuliskan "Circular Dependency", tetapi itu bukan kata-kata yang tepat, karena bagi saya desainnya tampak solid.
Namun, pertimbangkan skenario berikut ini, di mana bagian biru diberikan dari mitra eksternal, dan oranye adalah implementasi saya sendiri. Juga asumsikan ada lebih dari satu ConcreteMain, tetapi saya ingin menggunakan yang spesifik. (Pada kenyataannya, setiap kelas memiliki lebih banyak dependensi, tetapi saya mencoba menyederhanakannya di sini)

Skenario

Saya ingin instanciate semua ini dengan Depency Injection (Unity), tetapi saya jelas mendapatkan StackOverflowExceptionkode berikut, karena Runner mencoba untuk membuat Instantiate, dan ConcreteMain membutuhkan Runner.

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

Bagaimana saya bisa menghindari ini? Apakah ada cara untuk menyusun ini sehingga saya bisa menggunakannya dengan DI? Skenario yang saya lakukan sekarang adalah mengatur semuanya secara manual, tapi itu menempatkan ketergantungan yang sulit di ConcreteMaindalam kelas yang instantiate itu. Inilah yang saya coba hindari (dengan registrasi Unity di konfigurasi).

Semua kode sumber di bawah ini (contoh sangat sederhana!);

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
sumber

Jawaban:

10

Apa yang dapat Anda lakukan adalah membuat pabrik, MainFactory yang mengembalikan instance ConcreteMain sebagai IMain.

Kemudian Anda dapat menyuntikkan Pabrik ini ke konstruktor Runner Anda. Buat Main dengan pabrik dan lewati penginapan itu sendiri sebagai parameter.

Ketergantungan lain pada konstruktor ConcreteMain dapat diteruskan ke MyMainFactory melalui IOC dan didorong ke konstruktor beton secara manual.

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)
    }
}
ya
sumber
4

Gunakan wadah IOC yang mendukung skenario ini. Saya tahu bahwa AutoFac dan yang lainnya mungkin. Ketika menggunakan AutoFac, batasannya adalah bahwa salah satu dependensi harus memiliki PropertiesAutoWired = true dan menggunakan Properti untuk dependensi.

Esben Skov Pedersen
sumber
4

Beberapa kontainer IOC (misalnya Spring atau Weld) dapat menyelesaikan masalah ini menggunakan proxy yang dihasilkan secara dinamis. Proxy disuntikkan di kedua ujungnya dan objek nyata hanya dipakai saat proxy pertama kali digunakan. Dengan begitu, dependensi melingkar tidak menjadi masalah kecuali kedua objek memanggil metode satu sama lain dalam konstruktor mereka (yang mudah untuk dihindari).

vrostu
sumber
4

Dengan Unity 3, Anda sekarang dapat menyuntikkan Lazy<T>. Ini mirip dengan menyuntikkan cache Pabrik / objek.

Pastikan Anda tidak melakukan pekerjaan di ctor Anda yang membutuhkan penyelesaian ketergantungan Malas.

DSS539
sumber