理论
在一般情况下,如果一个类依赖于某些服务,那么可能会在内部去创建需要的服务:
public class Foo
{
ISomeService _service;
public Foo()
{
_service = new SomeService();
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
在项目规模比较小的时候这种做法无可厚非,但随着项目规模的扩大,Foo类与SomeService的紧耦合会显得缺少灵活性。
Foo应该只需要关心自身的实现并且确认服务提供了自身所需的接口即可,而不需要关心所选择的服务的实现细节:
public class Foo
{
ISomeService _service;
public Foo(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
在这种定义方式下,假设bar类使用了Foo类,那它也不用关心Foo如何使用SomeService的具体实现:
public class Bar
{
ISomeService _service;
public Bar(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
var foo = new Foo(_service);
foo.DoSomething();
...
}
}
使用依赖注入的方式,所需要的服务都需要等待外部来注入,在这张根据依赖关系而组成的“对象图”中,实现注入逻辑的部分就是它的”根部”(也就是前面文章说到的注入器),它看起来就类似:
var service = new SomeService();
var foo = new Foo(service);
var bar = new Bar(foo);
.. etc.
作为依赖注入框架,Zenject会帮助完成这一部分工作,因此程序员不需要再编写如以上的代码;
其他问题
使用DI要明白和理解使用它的优点是什么,其一就是由于约定了每个类都不去干涉所依赖服务的具体实现,带来的好处就是对于服务类接口的修改是很方便的(如上面的例子中要修改ISomeServer的内部实现逻辑,只要确保PerformTask的服务,那么Foo根本不需要任何的改动)。
另一个问题是运用DI时,会从每个类中抽出接口,并使用这些接口,而不是直接使用类,原本如此的定义的目的是为了使代码具有更松的耦合,然而大多数情况下,某个功能只有单一的,特点的类去实现,这种情况下使用接口只会增加不必要的开销,而且类自身已经提供了共有成员作为接口。
一个好的经验法则是:只有当一个类拥有一个以上的不同实现时,才为其创建接口(重用、抽象原则)。
具体的使用说明可以看官方Githttps://github.com/modesttree/Zenject#hello-world-example