讲IoC之前先说说DI。理解了DI再去理解IoC会变得相对简单一些。
由于IoC的意义比较晦涩难懂,软件界的泰斗人物Martin Fowler提出了DI(Dependency Injection)用于代替IoC概念。依赖注入确实更加贴切。
下面我们看一个经典案例:
有一台打印机,打印机有墨盒,有纸张。我们就用java来抽象一下打印机执行打印任务的功能。
首先,创建打印机类
package domo;
public class Printer {
private String id;
private String brand;
public void print() {
Ink ink = new Ink();
ink.setColor("黑色");
Paper paper = new Paper();
paper.setSpec("A4");
System.out.println(brand+"牌打印机"+id+"正在使用"+paper.getSpec()+
"纸和"+ink.getColor()+"墨盒打印。。。");
}
}
测试类
package test;
import domo.Ink;
import domo.Paper;
import domo.Printer;
public class PrinterTest {
public static void main(String[] args) {
Printer printer = new Printer();
printer.setId("001");
printer.setBrand("惠普");
printer.print(ink, paper);
}
}
以上代码输出如下结果
惠普牌打印机001正在使用A4纸和黑色墨盒打印。。。
显然以上代码实现了打印机打印的功能,但是如果哪天打印机的纸张类型或者墨盒颜色改变了,我们就只能修改代码来对应,然而这样大部分代码都会相同,而且每一次更换了纸张或者墨盒我们都需要去修改打印机的内部实现,这种做法显然是不合适的。这个时候我们想到如果纸张和墨盒可以单独定义,当打印机需要不用的墨盒时在调用打印方法时再给打印机传入相应的纸张和墨盒,这时候一旦纸张和墨盒改变时候并不会影响到打印机内部的实现。下面我们就照着这个思路修改以上代码:
首先修改打印机类,在其打印时只需要传入纸张和墨盒
package domo;
/*
* 打印机类
* @author kemir
*
*/
public class Printer {
private String id; //编号
private String brand; //品牌
private Ink ink;
private Paper paper;
public Ink getInk() {
return ink;
}
public void setInk(Ink ink) {
this.ink = ink;
}
public Paper getPaper() {
return paper;
}
public void setPaper(Paper paper) {
this.paper = paper;
}
public void print() {
System.out.println(brand+"牌打印机"+id+"正在使用"+paper.getSpec()+"纸和"+ink.getColor()+"墨盒打印。。。");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
创建打印机类中依赖的墨盒类和纸张类
package domo;
/*
* 墨盒类
* @author kemir
*
*/
public class Ink {
private String color; //颜色
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
package domo;
/*
* 纸张类
* @author kemir
*
*/
public class Paper {
private String spec; //规格
public String getSpec() {
return spec;
}
public void setSpec(String spec) {
this.spec = spec;
}
}
此时我们要在测试类中调用打印机的打印方法,只须传入纸张和墨盒的对象即可:
package test;
import domo.Ink;
import domo.Paper;
import domo.Printer;
public class PrinterTest {
public static void main(String[] args) {
Ink ink = new Ink();
ink.setColor("黑色");
Paper paper = new Paper();
paper.setSpec("A4");
Printer printer = new Printer();
printer.setId("001");
printer.setBrand("惠普");
printer.setPaper(paper);
printer.setInk(ink);
printer.print();
Printer printer1 = new Printer();
printer1.setId("001");
printer1.setBrand("惠普");
printer1.setPaper(paper);
printer1.setInk(ink);
printer1.print();
}
}
我们来看代码1和代码2有什么不同。在代码1中我在调用打印机的打印方法时创建了墨盒和纸张两个对象,我每次调用打印机打印方法时都需要主动去创建Ink和Paper对象,而代码2我们并没有主动去new Ink和Paper对象而是在print方法需要这两个对象的时候我们通过PrinterTest 类创建了Ink和Paper对象并通过打印机的set方法将创建的对象传给Printer。这就是DI的含义。这样Printer类就将Ink和Paper的对象创建(即控制权)交给了第三方类去执行。这就是IoC的含义。然IoC的内涵却不止于此。
IoC(Inverse of Control)是Spring的核心。AOP、声明式事务等功能都是建立在此基础之上。IoC包含丰富的内涵,它涉及到代码解耦、设计模式、代码优化等问题的考量。下面我们主要针对这三个方面对IoC进行探讨。
首先是代码解耦。上述代码并不能很好的说明这个问题,我们不妨想想代码2还可以如何优化,使其能更好的实现代码的解耦?