タイトルは「循環依存」と書いてありますが、デザインがしっかりしているように見えるので、これは正しい表現ではありません。
ただし、次のシナリオを検討してください。青い部分は外部パートナーから提供され、オレンジは私自身の実装です。また、複数のConcreteMain
があると仮定しますが、特定のものを使用したいと思います。 (実際には、各クラスにはいくつかの依存関係がありますが、ここでは簡略化しようとしました)
これらすべてをDepency Injection(Unity)でインスタンス化したいのですが、RunnerがConcreteMainをインスタンス化しようとしているため、ConcreteMainにはRunnerが必要なので、次のコードでStackOverflowException
を取得します。
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));
}
}
できることは、IMainとしてConcreteMainのインスタンスを返すMainFactoryというファクトリを作成することです。
次に、このファクトリをRunnerコンストラクターに注入できます。ファクトリを使用してメインを作成し、イン自体をパラメータとして渡します。
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)
}
}
Unity 3では、Lazy<T>
。これは、ファクトリ/オブジェクトキャッシュの注入に似ています。
Lazy依存関係の解決を必要とするctorで作業しないようにしてください。
このシナリオをサポートするIOCコンテナを使用します。AutoFacと他の可能なコンテナはそうであることを知っています。AutoFacを使用する場合、制限の1つは、依存関係の1つにPropertiesAutoWired = trueがあり、依存関係のプロパティを使用する必要があることです。 。
一部のIOCコンテナ(たとえば、SpringまたはWeld)は、動的に生成されたプロキシを使用してこの問題を解決できます。プロキシは両端に注入され、実際のオブジェクトは、プロキシが最初に使用されたときにのみインスタンス化されます。方法として、2つのオブジェクトがコンストラクタで互いにメソッドを呼び出さない限り、循環依存は問題になりません(これは簡単に回避できます)。