依存関係注入で「循環依存関係」を処理する方法


15

タイトルには「Circular Dependency」と書かれていますが、それは正しい表現ではありません。私にはデザインがしっかりしているように見えるからです。
ただし、次のシナリオを検討してください。青い部分は外部パートナーから提供され、オレンジは私自身の実装です。また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

できることは、ConcreteMainのインスタンスをIMainとして返すMainFactoryファクトリを作成することです。

次に、このファクトリをRunnerコンストラクタに注入できます。ファクトリーでMainを作成し、パラメーターとしてinn自体を渡します。

ConcreteMainコンストラクターの他の依存関係は、IOCを介してMyMainFactoryに渡され、具象コンストラクターに手動でプッシュできます。

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を使用する場合、依存関係の1つがPropertiesAutoWired = trueを持ち、依存関係にプロパティを使用する必要があるという制限があります。


4

一部のIOCコンテナ(SpringやWeldなど)は、動的に生成されたプロキシを使用してこの問題を解決できます。プロキシは両端に注入され、実際のオブジェクトは、プロキシが最初に使用されたときにのみインスタンス化されます。そうすれば、2つのオブジェクトがコンストラクターで互いにメソッドを呼び出さない限り、循環依存関係は問題になりません(簡単に回避できます)。


4

Unity 3では、を注入できるようになりましたLazy<T>。これは、ファクトリ/オブジェクトキャッシュの挿入に似ています。

Lazy依存関係の解決を必要とするctorで作業しないようにしてください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.