DI是什么?
Dependency Injection 常常简称为:DI。
它是实现控制反转(Inversion of Control – IoC)的一个模式。
fowler 大大大神 “几十年”前的经典文章 https://www.martinfowler.com/articles/injection.html 说的很清楚。
“几十年”以来,相信大家都早已学会了 大大大神 的教典。
我们简单回忆一下对应内容,以便我们可以顺利进入后续章节:徒手撸个小DI。
文章内容大致是这样:
首先举例:
public interface MovieFinder {
List findAll();
}
class MovieLister {
private MovieFinder finder;
public MovieLister() {
finder = new ColonDelimitedMovieFinder("movies1.txt");
}
public Movie[] moviesDirectedBy(String arg) {
List allMovies = finder.findAll();
for (Iterator it = allMovies.iterator(); it.hasNext();) {
Movie movie = (Movie) it.next();
if (!movie.getDirector().equals(arg)) it.remove();
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
然后大大大神吐槽了一堆:
这个实现类的名字就说明:我将要从一个逗号分隔的文本文件中获得影片列表。你不必操心具体的实现细节,只要设想这样一个实现类就可以了。如果这个类只由我自己使用,一切都没问题。但是,如果我的朋友叹服于这个精彩的功能,也想使用我的程序,那又会怎么样呢?如果他们也把影片清单保存在一个逗号分隔的文本文件中,并且也把这个文件命名为” movie1.txt “,那么一切还是没问题。如果他们只是给这个文件改改名,我也可以从一个配置文件获得文件名,这也很容易。但是,如果他们用完全不同的方式——例如SQL 数据库、XML 文件、web service,或者另一种格式的文本文件——来存储影片清单呢?在这种情况下,我们需要用另一个类来获取数据。由于已经定义了MovieFinder接口,我可以不用修改moviesDirectedBy方法。但是,我仍然需要通过某种途径获得合适的MovieFinder实现类的实例。
还有张依赖图
MovieLister类既依赖于MovieFinder接口,也依赖于具体的实现类。我们当然希望MovieLister类只依赖于接口,但我们要如何获得一个MovieFinder子类的实例呢?
在Patterns of Enterprise Application Architecture一书中,我们把这种情况称为插件(plugin):MovieFinder的实现类不是在编译期连入程序之中的,因为我并不知道我的朋友会使用哪个实现类。我们希望MovieLister类能够与MovieFinder的任何实现类配合工作,并且允许在运行期插入具体的实现类,插入动作完全脱离我(原作者)的控制。这里的问题就是:如何设计这个连接过程,使MovieLister类在不知道实现类细节的前提下与其实例协同工作。
将这个例子推而广之,在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。但是,如果我们希望以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。所以,现在的核心问题就是:如何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion of Control)模式。
学术一点就是说 避免类之间强耦合,我们需要用依赖注入等方式在运行时才建立依赖达到代码松耦合,从而使代码易为维护
戏言就是在说:
- 我们都是大忙人,请你作为一个类简单明了的说清楚 : 你这个类能干什么事? 不要让我们这些大忙人把你每件衣服一件一件看完了才知道你是木匠, 还是铁匠
- 我们都是大老板,我们财产不能全靠你一个,你不能干活或者你干不好活,我们做老板的人必须能找人换了你
所以上述代码中:
我(MovieLister)离不开了 你 (ColonDelimitedMovieFinder("movies1.txt")),
但是我们男人必须靠自己,至少表面没人看出我们之间的关系
只有从我们(MovieLister)身体里面没有了你,才能没人看出我们之间的关系
当我们开始干活的时候,我们再根据我们的私下关系协调好工作,男女搭配,好好干活。
说到这里, 各位要被面试的同学记好这些话, 不要被问到依赖注入帮我解决了什么事情的时候, 回一句 我们不用自己new 对象啦, 这样大家就不会看见面试官无语又懵逼的脸了。
依赖注入的几种形式
- 构造函数注入
class MovieLister
{
private IMovieFinder finder;
public MovieLister(IMovieFinder finder) {
this.finder = finder;
}
}
- 属性注入
class MovieLister
{
[Inject]
public IMovieFinder Finder { get; set;}
}
- 接口注入
public interface InjectFinder
{
void injectFinder(MovieFinder finder);
}
class MovieLister : InjectFinder
{
private IMovieFinder finder;
public void injectFinder(MovieFinder finder)
{
this.finder = finder;
}
}
这几种方式之间并没有性能或者什么特别的优势,主要是形式上的差异。
具体对比可以参考 http://insights.thoughtworkers.org/injection/
下一章: 手把手教你写DI_1_DI框架有什么?
引用参考:
- http://insights.thoughtworkers.org/injection/
- https://www.martinfowler.com/articles/injection.html