依赖注入(DJ, Dependency Injection)和控制反转(IOC,Inversion Of Control)听上去很高大上,其实非常简单。考虑下面这个场景:
举一个最简单的例子:有一个汽车流水线,流水线上有很多机器人,各自负责汽车一个部件的装配。按照正常人朴素的逻辑,Robot是依赖于Car的。
下面代码是最简单的Car的模型,
public class Car{
private int id;
public Car(int num){id=num;}
public void addA(){System.out.println(this+" install A");}
public void addB(){System.out.println(this+" install B");}
public void addC(){System.out.println(this+" install C");}
}
然后Robot有很多种抽象方式。最简单的依赖关系,就是把Car作为Robot函数的参数传进去。最后的start()函数,从创建新车对象,到装配A,B,C部件一气呵成,最后返回一辆装配好的汽车。
public class Robot{
public Robot doA(Car c){
c.addA();return this;
}
public Robot doB(Car c){
c.addB();return this;
}
public Robot doC(Car c){
c.addC();return this;
}
public Car start(){ //一条龙服务
Car c=new Car(++count);
doA(c).doB(c).doC(c);
return c;
}
}
这样的一条龙服务当然好,但既然所有操作都依赖于一个Car对象,可以把Car作为一个私有成员字段组合进来。
public class Robot{
private static int count=0;
Car car=new Car(++count); //Car作为成员字段
public Robot doA(){
car.addA();return this;
}
public Robot doB(){
car.addB();return this;
}
public Robot doC(){
car.addC();return this;
}
public Car start(){
doA().doB().doC();
return car;
}
}
但上面这种做法代码是简洁了,但后果就是Car和Robot的耦合度很高。而且每生产一辆新车,都要创建一个新的机器人。为了给Robot和Car解耦,就要用到 依赖注入(Dependency Injection)。其实很简单,把Car作为Robot构造函数的一个参数,就把每次都变化的Car部分独立出去了。每次不同的Car来,Robot都重复相同的装配动作。而把创建和分配Car对象的职责剥离出去,交给某个宏观控制模块来做。这样Car和Robot两个类就彻底解耦了。所以这里所谓依赖注入,注入的就是Car对象。
public class Robot{
private Car car=null;
public Robot(Car c){car=c}; //Car这个变化因素,交由外部统一分配。
public Robot doA(){
car.addA();return this;
}
public Robot doB(){
car.addB();return this;
}
public Robot doC(){
car.addC();return this;
}
public Car start(){
doA().doB().doC();
return car;
}
}
再进一步打磨一下,如果要让生产线仿真程度更高的话,可以不用Robot的构造函数来注入Car对象,而改用配置函数,我们给他取个名字比如说叫“绑定”,binding()。用来模拟机器人绑定汽车的过程。
public class Robot{
private Car car=null;
public void binding(Car c){car=c}; //改由配置函数注入依赖
public void desBinding(){car=null}; //解除绑定
public boolean isBinded(){return car!=null;} //检查是否绑定汽车
public Robot doA(){
car.addA();return this;
}
public Robot doB(){
car.addB();return this;
}
public Robot doC(){
car.addC();return this;
}
public Car start(){
doA().doB().doC();
return car;
}
}
这样就需要由一个外部调度类负责产生Car对象,并把Car对象分配给Robot来装配。由于Robot和Car完全解耦,系统可以轻易地扩展任意数量的Robot一起工作。
public class Controller{
public static void main(String[] args){
Robot r1=new Robot();
Robot r2=new Robot();
List<Car> products=new ArrayList<Car>();
for(int i=0;i<10;i+=2){
r1.binding(new Car(i));
r2.binging(new Car(i+1));
products.add(r1.start());
products.add(r2.start());
r1.desBinding();
r2.desBinging();
}
}
}
这样的系统,即使在更加复杂的并发编程的场景下,重构起来也相当灵活。考虑下面这个稍微复杂的并发场景,
流水线上有机器人A,B,C。分别负责装配汽车部件a,b,c。一辆汽车在流水线上,依次经过机器人A,B,C并最终装配完成。
首先,并发场景,最好是用的就是BlockingQueue来协调不同机器人的并发工作。这个场景就被抽象为:
注意,这里对BlockingQueue的使用,本身已经是控制反转的一种手段。因为它已经把RobotA,B,C从三者的交互协作中解耦,变成只面向两个队列的独立部件。而BlockingQueue又保证了并发状态下的串行化。并发过程因此被极大简化。
然后再根据依赖注入的原则,给每个Robot内部分别两个BlockingQueue字段,一个原料入队列,一个成品出队列。Robot类本身不负责初始化这两个队列,而把初始化绑定队列工作交给一个中央控制器。每个Robot都被设计成能够被装配任意两个队列。最后的代码如下:
public class Car{
private static int count=0;
private final int id=++count;
public void addA(){System.out.println(this+" install A");}
public void addB(){System.out.println(this+" install B");}
public void addC(){System.out.println(this+" install C");}
public String toString(){return "Car#"+id;}
}
public abstract class Robot implements Runnable{
private static int count=0;
private final int id=++count;
private Car car=null;
public void binding(Car c){car=c};
public void desBinding(){car=null};
public boolean isBinded(){return car!=null;}
public BlockingQueue<Car> inQueue;
public BlockingQueue<Car> outQueue;
public Robot(BlockingQueue<Car> in, BlockingQueue<Car> out){
inQueue=in;
outQueue=out;
}
public String toString(){return "Robot#"+id;}
public Car getCar(){return car;}
public void run(){}
}
public class RobotA extends Robot{
public RobotA(BlockingQueue<Car> in,BlockingQueue<Car> out){
super(in,out);
}
public void doA(){car.addA();}
public void run(){
try{
while(!Thread.interrupted()){
binding(inQueue.take()); //block
doA();
outQueue.put(getCar()); //block
desBinding();
}
}catch(InterruptedException ie){
System.out.println(this+" interrupted!");
}
System.out.println(this+" exit!");
}
}
public class RobotB extends Robot{
public RobotB(BlockingQueue<Car> in,BlockingQueue<Car> out){
super(in,out);
}
public void doB(){car.addB();}
public void run(){
try{
while(!Thread.interrupted()){
binding(inQueue.take()); //block
doB();
outQueue.put(getCar()); //block
desBinding();
}
}catch(InterruptedException ie){
System.out.println(this+" interrupted!");
}
System.out.println(this+" exit!");
}
}
public class RobotC extends Robot{
public RobotC(BlockingQueue<Car> in,BlockingQueue<Car> out){
super(in,out);
}
public void doC(){car.addC();}
public void run(){
try{
while(!Thread.interrupted()){
binding(inQueue.take()); //block
doC();
outQueue.put(getCar()); //block
desBinding();
}
}catch(InterruptedException ie){
System.out.println(this+" interrupted!");
}
System.out.println(this+" exit!");
}
}
public class Controller{
public static void sleep(int time){
try{
TimeUnit.MILLISECONDS.sleep(time);
}catch(InterruptedException ie){
System.out.println("Controller interrupted!");
}
}
public static void main(String[] args){
ExecutorService exec=Executors.newCachedThreadPool();
BlockingQueue<Car> queue1=new LinkedBlockingQueue<Car>();
BlockingQueue<Car> queue2=new LinkedBlockingQueue<Car>();
BlockingQueue<Car> queue3=new LinkedBlockingQueue<Car>();
BlockingQueue<Car> queue4=new LinkedBlockingQueue<Car>();
for(int i=0;i<10;i++){
try{
queue1.put(new Car());
}catch(InterruptedException ie){
System.out.println("Error during car creation!");
}
}
exec.execute(new RobotA(queue1,queue2));
exec.execute(new RobotB(queue2,queue3));
exec.execute(new RobotC(queue3,queue4));
sleep(5000);
exec.shutdownNow();
}
}
通过依赖注入,整个流水线的耦合度变得非常地低。每个模块都高度独立,比如每个Robot可以和任意的两个BlockingQueue队列拼接,每个BlockingQueue可以被插入任意数量的Car对象。整条流水线也可以由任意数量的Robot以任意的顺序拼装在一起,从而组成各种流程完全不同的流水线。而且此流水线的扩展非常简单,可以随时添加各种任意功能的新型Robot,并且完全不用担心并发场景下的资源冲突问题,BlockingQueue保证了整个过程的线程安全。试想一下,如果还是以老式的wait(),notifyAll()方式让Robot互相协作,并发场景将变得非常复杂。或者以传统的组合方法,把BlockingQueue组合进Robot里,并让Robot自己初始化BlockingQueue,那么生产线和Robot之间就高度耦合,整条生产线就被写死了,不能实现自由的拼装。
总之,控制反转,依赖注入技术,我认为比绝大多数的设计模式都要重要。因为它体现的不是某种固化的模式,而是从普通事物中分离出“变化项”和“不变项”,然后独立出“变化项”,保留“不变项”的设计原则。掌握了这个思想,就很容易在抽象过程中,设计出低耦合的灵活系统。