65.9K
CodeProject 正在变化。 阅读更多。
Home

Ninject 如何帮助解决循环依赖

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017年2月21日

CPOL

2分钟阅读

viewsIcon

13025

展示了一种使用 Ninject 和辅助类解决循环依赖的一般技术。

引言

循环依赖是软件工程中常见的问题。 在本技巧中,我将解释为什么会出现循环依赖,以及如何使用 Ninject 和一些辅助类来解决它。 使用此方法可以防止异常的发生。

背景

要理解代码,一些基本的 Ninject 经验会有所帮助。

Using the Code

循环依赖的概念非常简单。 有一个 ClassA 依赖于 ClassB,并且需要 ClassB 的实例才能完成其工作。 因此,将 ClassB 的实例作为构造函数参数是显而易见的。 然而,反过来也是可能的。 那么应该先创建哪个? 从编译时来看,这个问题很容易解决。 如果 ClassAClassB 位于不同的程序集中,你的代码将无法编译。 编译器需要先构建 ClassA 的程序集,因为 classB 需要它,但反过来也是可能的。 在这种情况下,只需使用 GoF 原则:“编程到接口,而不是实现”。 如果这两个类彼此之间并不直接了解,而只是知道彼此的接口,你的编译器就不会抱怨。

然而,在运行时,仍然存在问题。 应该先创建哪个? 无论如何,它们在运行时都需要彼此...... 懒加载的概念可以帮助我们。 这是我们的类:

public class Class1 : IClass1
{
	private readonly Lazy<IClass2> objectTwo;
	public Class1(Lazy<IClass2> objectTwo)
	{
	    this.objectTwo = objectTwo;	
	}
	
	public int ReturnOne()
	{
	    return 1;
	}
	
	
	public void DoSomething()
	{
	    var value = this.objectTwo.Value.ReturnTwo();
	    Console.WriteLine($"{value}+{this.ReturnOne()}");
	}	
}

public class Class2 : IClass2
{
	private readonly Lazy<IClass1> objectOne ;
	public Class1(Lazy<IClass1> objectOne )
	{
    		this.objectOne = objectOne;
	}

	public int ReturnTwo()
	{
   	 	return 2;
	}


	public void DoSomething()
	{
    		var value = this.objectTwo.Value.ReturnTwo();
    		Console.WriteLine($"{value}+{this.ReturnOne()}");
	}
}

在这里,你可以看到懒加载如何解决问题。 你可以使用基于构造函数的依赖注入。 你不需要一个需要在之后调用的 setter 方法,并且可以被重新调用(在许多情况下,你不想这样做)。 首先创建的实例只是首先使用的类的实例。 你不必担心它。 一旦需要一个实例,它就存在了。

这是我们的 Ninject 模块:

public class MyModule : NinjectModule
{
	public override void Load()
	{	
	    this.Bind<IClass1>().To<Class1>();
	    this.Bind<IClass2>().To<Class2>();
	    this.BindLazy<IClass1>().To<Class1>();
	    this.BindLazy<IClass2>().To<Class2>();	
	}
}

如你所见,我们 Ninject 模块中的代码非常易于使用。 此外,除了 Bind 之外,你还使用了一个“BindLazy”。 问题解决了,不需要 setter,不需要额外的属性,也不需要担心先创建什么。

为了确保“BindLazy”有效,添加以下代码:

public static class NinjectExtensions
{
	public class LazyCombi<T>
	{
		public IBindingToSyntax<Lazy<T>> LazyBindingSyntax { get; private set; }
		public NinjectModule Module { get; private set; }
		
		public LazyCombi(NinjectModule module, 
		IBindingToSyntax<Lazy<T>> lazyBindingSyntax)
		{
			this.Module = module;
			this.LazyBindingSyntax = lazyBindingSyntax;
		}
		
           	public IBindingWhenInNamedWithOrOnSyntax<Lazy<T>> To<TImplementation>() 
                   where TImplementation : T
		{
			return this.LazyBindingSyntax.ToMethod(c =>
			{
				return new Lazy<T>(() =>
				{
					return this.Module.Kernel.Get<TImplementation>();
				}
				
			});
		}
		
		public static LazyCombi<T> BindLazy<T>(this NinjectModule module)
		{
			return new LazyCombi<T>(module, module.Bind<Lazy<T>>());		
		} 
	}	
}

关注点

这个问题众所周知,我发现很多人使用 setter 方法或属性来解决这个问题。 然而,使用 setter 进行依赖注入是修改依赖。 有时,这很有用,但在许多情况下,你希望确保它只注入一次。 基于构造函数的依赖注入更好,并且通过懒加载,这对于循环依赖也是可能的。 使用 setter 有缺点,可以通过基于构造函数的依赖注入来避免。 如果你不想修改你的对象,并且希望你的应用程序是线程安全的,请避免使用 setter,除非你确定你需要可变状态,否则 setter 只是冗余代码。

测试 Ninject 模块:

static void Main(string[] args)
{
	var firstKernel = new StandardKernel(new MyModule());
	var objectOne = firstKernel.Get<IClass1>();
	objectOne.DoSomething();
	var secondKernel = new StandardKernel(new MyModule());
	var objectTwo = secondKernel.Get<IClass2>();
	objectTwo.DoSomething()
	Console.ReadLine();
}
© . All rights reserved.