|     java design pattern dj ioc   |    

依赖注入(DJ, Dependency Injection)和控制反转(IOC,Inversion Of Control)听上去很高大上,其实非常简单。考虑下面这个场景:

举一个最简单的例子:有一个汽车流水线,流水线上有很多机器人,各自负责汽车一个部件的装配。按照正常人朴素的逻辑,Robot是依赖于Car的。

carRobot

下面代码是最简单的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之间就高度耦合,整条生产线就被写死了,不能实现自由的拼装。

总之,控制反转,依赖注入技术,我认为比绝大多数的设计模式都要重要。因为它体现的不是某种固化的模式,而是从普通事物中分离出“变化项”和“不变项”,然后独立出“变化项”,保留“不变项”的设计原则。掌握了这个思想,就很容易在抽象过程中,设计出低耦合的灵活系统。