您的位置:首页 > 编程语言 > Java开发

Spring 4 学习笔记2:控制反转(IoC)和依赖注入(DI)

2015-12-23 08:15 1051 查看

一、介绍

学习Spring的伙伴都知道控制反转(Inversion of Control)依赖注入(Dependency Injection),而且这两个概念也是Spring最基础的东西,贯穿了整个Spring框架。但是,初学Spring的人大都会被这两个概念搞混,不知道其异同(我在前一篇文章也把两者认为是同一个东西)。所以,本文将讨论控制反转和依赖注入的概念以及他们的关系。首先,把两者的关系总结出来,因为我们需要带着这个概念去看下面的文章。带着问题去学习效率更高,学习效果更好。

关系控制反转是更抽象的原则,而依赖注入是更具体的实现。依赖注入只是用以实现控制反转的一种实现方式而已。

二、依赖(Dependency)

既然依赖注入只是控制反转的一种具体的实现,所以我们前面的讨论都只讨论控制反转,最后讨论依赖注入。要理解控制反转,首先我们需要理解什么是依赖。如图1,如果Class A使用了Service A的某个方法或整个Service A,那么说明Service A就是Class A的一个依赖。比如我们在开发Web应用时,在控制器(Controller)中使用某个服务(Service),那么这个服务就是这个控制器的依赖。



图1 Class A依赖于Service A

三、控制反转

使用来自维基百科的定义:

Inversion of Control is an abstract principal describing an aspect of some software architecture design in which the flow of control of a system is inverted in comparison to procedural programming.

也就是说控制反转其实是把系统中的流程反转了的一种抽象的原则,说了和没说一样。下面就通过例子来解释这个概念。注意:这些都只是代码片段,并不完整。

3.1 传统编程方式

// Example.java
public class Example {

private ConsoleLogger logger;

public void doStuff() {
// 对ConsoleLogger的依赖
logger = new ConsoleLogger();
logger.log();
}
}

// ConsoleLogger.java
public class ConsoleLogger {

public void log() {};
}

// TestMain.java 测试Example类
public class TestMain {

public static void main(String[] args){

Example example = new Example();
example.doStuff();
}
}


我们来分析这种方式下会有那些问题

每一个
Example
类都拥有一个
ConsoleLogger
的备份,如果
ConsoleLogger
的新建非常昂贵的话(比如包含数据库打开,关闭连接操作),那么这是非常影响效率的。

因为在
Example
类中包含
ConsoleLogger
的实例化操作(使用
new
操作符),所以如果我们更改了
ConsoleLogger
的实现方式,那么我们必须去每个
Example
类中修改源代码。

如果我们现在需要修改
Example
类,不使用
ConsoleLogger
了,而是使用
FileLogger
,这样问题就变得复杂,需要去修改涉及的
Example
源码。

这会让
Example
类变得难以测试。因为
Example
类依赖于具体的
ConsoleLogger
类,这样你就不能仿制一个假的
ConsoleLogger
类去测试。而且,如果你的
ConsoleLogger
类依赖于具体的环境的话,那么你的
JUnit
测试可能在某个环境中可以通过,但是会在其他的环境中失败。

现在,我们就来想办法如何解决这些问题。首先,我们想既然会使用不同的依赖实现方式-
ConsoleLogger
FileLogger
或者其他方式。所以,我们不应该在
Example
类中依赖具体的实现类,而应该依赖于抽象。所以,我们建一个抽象的
Logger
接口:

// Logger.java
public interface Logger {
void log();
}

// ConsoleLogger和FileLogger类分别继承Logger接口,此处省略
// 修改后的Example.java类
public class Example {

private Logger logger;

public void doStuff() {
// 对ConsoleLogger的依赖
logger = new ConsoleLogger();
logger.log();
}
}


如此修改之后,如果我们需要修改使用不同的
Logger
实现,只需要把上述
Example
类中的
new ConsoleLogger()
换成其他
Logger
实现就行了。但是,这样也不能满足不修改源码实现使用不同
Logger
实现的需求。既然需要不同的实现,我们能不能使用一个标识符来进行判断,从而达到选择不同
Logger
实现的目的。

// Example.java 添加标识符判断使用不同Logger实现
public class Example {

private Logger logger;

public Example(int flag) {
if (0 == flag) {
logger = new FileLogger();
} else {
logger = new ConsoleLogger();
}
}

public void doStuff() {
// 其它的Example代码逻辑
...
logger.log();
...
}
}

// TestMain.java 还需要修改使用Example类的地方
public class TestMain {

public static void main(String[] args){
// 使用FileLogger
Example example = new Example(0);
example.doStuff();

// 使用默认的ConsoleLogger
Example example = new Example();
example.doStuff();
}
}




这样修改以后,是不是觉得好多了。通过传入不同的参数就可以选择不同的
Logger
实现。但是,这样还是没有解决上面提到的那些问题。
Example
自己决定依赖的实现方式,自己实例化依赖。这样的结果就是Client(
Example
类)和Service(
Logger
的具体实现)之间的耦合,也就导致了以上我们列出的问题。所以,如何解决这个问题呢,方案就是控制反转

3.2 控制反转

下面我们使用控制反转来解决这些问题

// Example.java 修改Example类,不在让其决定使用哪个Logger实现
public class Example {

private Logger logger;

public Example(Logger logger) {
this.logger = logger;
}

public void doStuff() {
...// 其它的Example代码逻辑
logger.log();
...
}
}

// TestMain.java 还需要修改使用Example类的地方
public class TestMain {

public static void main(String[] args){
// 使用FileLogger
Example example = new Example(new FileLogger());
example.doStuff();

// 使用ConsoleLogger
Example example = new Example(new ConsoleLogger());
example.doStuff();
}
}




从上面的代码我们可以看到,决定具体使用哪个
Logger
实现已经不是
Example
自己来决定了,而是由其他的实体(在Spring中是IOC容器)来决定,这样就实现了控制反转,这就是所谓的控制反转。既然,控制反转搞清楚了,就来看看到底解决我们的问题没有。

因为在运行之前,
Example
都不知道
Logger
的具体实现,这样我们就可以重复使用现成的
Logger
实现,节省了资源。

因为现在
Example
根本不知道
Logger
的实现,所以,我们可以根据需要随时在使用时更换使用哪种
Logger
实现。实现了Client(
Example
)和Service(
Logger
)的解耦,提高了代码的可重用性。

因为在运行时期才确定
Logger
实现,所以我们可以使用仿制(mock)的Service去测试
Example
,使
Example
易于测试,达到了独立测试的目的。

减少了很多重复代码。当你使用传统模式实现时,当项目规模变得越来越大后,代码的重复就越多。而使用了控制反转就减少了很多重复的代码。

我们已经清楚的明白了什么是控制反转,但是什么是依赖注入呢?它和控制反转的具体关系是什么?

四、控制反转的实现方式

前面,我们讨论了什么是控制反转。所以,任何可以达到控制反转的实现都属于控制反转这个原则之下,那么到底有哪些方式呢。

使用工厂模式(Factory pattern)

使用服务定位器(Service Locator pattern)

使用依赖注入(Dependency Injection)

构造器注入(Constructor Injection)

Setter方法注入

接口注入(Interface Injection)

使用上下文查找(contextualized lookup)

使用模板方法设计模式(template method design pattern)

使用策略设计模式(strategy design pattern)

从上面的方式我们看到依赖注入只是控制反转的一种方式,而我们在讨论控制反转例子中使用的实现控制反转的方式就是依赖注入中的构造器注入。至于什么是依赖注入我会在下一篇文章中介绍,也会介绍依赖注入的三种方式。

参考

Inversion of Control Containers and the Dependency Injection pattern

跟我一起学Spring 3(4)–深入理解IoC(控制反转)和DI(依赖注入)

IOC ( Inversion of Control) explained in a simple way

Inversion of Control

Inversion of Control: Overview with Examples

Inversion of Control and Dependency Injection design pattern with real world Example - Spring tutorial
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring ioc