参考:https://www.tutorialsteacher.com/ioc
作者本人在学习Ioc时,始终不理解IoC的本质什么,很多中文资料在我看来比较难懂,于是搜索英文资料,终于找到一篇比较浅显易懂的文章。原文代码是C#写的,本人用Java重写。
1. 简介
你可能听说过以下几个名词:
中文 | 英文 | 英文缩写 |
---|---|---|
控制反转 | Inversion of Control | IoC |
依赖倒转原则 | Dependency Inversion Principle | DIP |
依赖注入 | Dependency Injection | DI |
IoC容器 | Ioc Container | 无 |
但是你可能不清楚他们之间的区别。
如上图所示,IoC和DIP是处于上层的设计原则(Principle),也正因为是原则,所以没有提供具体的实现细节,这些原则是我们设计类时需要遵循的原则。DI是模式(Pattern),Ioc容器是框架(Framework)。
让我们先简单过一下上面的几个概念。
- 控制反转 IoC
IoC是一种设计原则,它建议在面向对象设计中反转(Inversion)各种控制(Control),以达到在类之间的松耦合(Loose Coupling)。在这里,控制更多指的是类的附加职责,而不是它的主要职责,比如控制应用的流程或控制所依赖类的创建和绑定。
- 依赖倒转原则 DIP
DIP同样可以达到类之间的松耦合,强烈建议同时使用IoC和DIP来达到松耦合设计。
DIP建议高层模块(high-level modules)不应该依赖低层模块(low-levle modules),两者都应该依赖抽象(abstaction, )。在java中,抽象指的是抽象类或者接口。
- 依赖注入 DI
DI是一种设计模式(Pattern),用来实现IoC,以达到反转依赖类的创建的目的。
- IoC容器
IoC容器是一种框架,用来管理应用程序中的自动依赖注入,因此我们程序猿可以节省体力。著名的spring中就提供IoC容器ApplicationContext。
下图所示,我们一步一步地学习从耦合类到松耦合类的设计过程。
强耦合类 -> 使用工厂模式实现IoC -> 通过创建抽象实现DIP -> 实现DI -> 使用IoC容器 - > 松耦合类。
2. 控制反转 IoC
2.1 概念的理解
学习一个东西,首先了解概念。先解释一下控制反转的概念,分成控制和反转两个部分。
控制:更多指的是控制类的附加职责,而不是它的主要职责,比如控制应用的流程或控制所依赖类的创建和绑定。
反转:指的将附加职责交给其他类来完成,而不是自己来完成。
比如,你开车去上班,那么意味着你控制着车,反转就是你不自己开车,而是坐的士去上班,车的控制权从你的手上转移到了出租车司机,你就不用操心开车的事,不用操心车停哪里,不用操心车的保养维修等等,你只需要关注自己的工作即可。
个人的简单理解:聚焦于自己主要的职责,把次要的职责外包出去,以此达到类之间松耦合的目的。
2.2 例子说明
public class A {
private B b;
public A() {
b = new B();
}
public void task() {
// do something here
b.someMethod();
// do something here
}
}
public class B {
public void someMethod() {
// do something
}
}
如上面的代码,A调用B类的someMethod()方法,换句话说A依赖于B。A类直接控制B类的创建和生命周期。Ioc原则推荐反转控制,就是说把B类的创建交给其他类。如下代码:
public class A {
private B b;
public A() {
b = Factory.getObjectOfB();
}
public void task() {
// do something here
b.someMethod();
// do something here
}
}
public class B {
public void someMethod() {
// do something
}
}
public class Factory {
public static B getObjectOfB() {
return new B();
}
}
你可以看到B类的创建已经交给Factory了,A只需要拿来用就行,专注于自己的主要职责task()方法。这就是控制反转。
3. 依赖倒转原则 DIP
3.1 定义
- 高层模块不应该依赖底层模块,两者都应该依赖抽象;
- 抽象不应该依赖细节,细节应该依赖抽象。
3.2 例子
public class CustomerBusinessLogic {
public CustomerBusinessLogic() {
}
public String getCustomerName(int id) {
DataAccess dataAccess = DataAccessFactory.GetDataAccessObj();
return dataAccess.getCustomerName(id);
}
}
public class DataAccess {
public DataAccess() {
}
public String getCustomerName(int id) {
// get it from DB in real app
return "Dummy Customer Name";
}
}
public class DataAccessFactory {
public static DataAccess GetDataAccessObj() {
return new DataAccess();
}
}
以上的例子,我们用简单工厂方法实现IoC原则,但是CustomerBusinessLogic直接用DataAccess,还是紧耦合。
正如DIP定义,高层模块不应该依赖低层模块,所谓高层模块就是依赖其他模块的模块,例如这里的CustomerBusinessLogic,那么被依赖的DataAccess就是低层模块;另外一个原则是抽象不依赖细节,细节依赖抽象。这里的抽象是什么? 在java中,就是抽象类和接口。
public class CustomerBusinessLogic {
private IDataAccess dataAccess;
public CustomerBusinessLogic() {
this.dataAccess = DataAccessFactory.GetDataAccessObj();
}
public String getCustomerName(int id) {
return dataAccess.getCustomerName(id);
}
}
public interface IDataAccess {
String getCustomerName(int id);
}
public class DataAccess implements IDataAccess {
public DataAccess() {
}
@Override
public String getCustomerName(int id) {
// get it from DB in real app
return "Dummy Customer Name";
}
}
public class DataAccessFactory {
public static IDataAccess GetDataAccessObj() {
return new DataAccess();
}
}
如上,高层模块CustomerBusinessLogic和低层模块DataAccess都依赖于抽象IDataAccess,细节DataAccess依赖于抽象IDataAccess。
4. 依赖注入DI
DI是一种设计模式,使用DI,我们可以将类的创建和绑定在依赖类的外部完成。简单的说A依赖于B,B的创建和绑定在A类的操作都不在A类内完成,而是其他类中完成。
实现DI涉及到三种类:
- client class:客户端类,依赖服务类的类;
- service class:服务类,给client提供服务的类;
- injector class:注入类,创建服务类,并且把服务类注入客户端类的类。
4.1 依赖注入的类型
有三种方式实现依赖注入:
- Constructor Injection:构造函数注入;
- Property Injection:属性注入,也即setter方法注入;
- Method Injection:方法注入,