以下文字来源于《从java走向javaee》。
在模块设计时,高层的抽象模块通常是与业务相关的模块,它应该具有重用性,而不依赖于低层的模块,例如如果低层模块原先是磁盘存取模式,而高层模块是个存档备份的需求,如果高层模块直接调用低层模块的方法,则就对低层模块产生了依赖关系。
例如下面这段代码:
//...
void save(){
//...
saveToFloppy();
}
由于save()方法依赖于saveToFloppy(),如果要更换低层的存储模式为USB,则这个方法没有办法重用,必须加以修改才行,低层模式的更改造成了上层模式也必须跟着改变,这不是一个良好的设计方式,在设计上希望模式都依赖于模式的抽象,这样才可以重用上层的业务设计。如果以面向对象的方式来设计,依赖反转(Dependecy Inversion)解释变为方法不应该依赖实现,而是依赖于抽象,实现必须依赖于抽象。来看看下面这个java方法:
public class BusinessObject{
private FloppyWriter writer=new FloppyWriter();
//...
public void save(){
//...
writer.saveToFloppy();
}
}
在这个方法中,BusinessObject的存档依赖于实际的FloppyWriter,如果想要改为存至USB,则必须修改或继承BusinessObject,而无法直接使用BusinessObject。
如果通过结构,可以改进这种情况,例如:
public interface IDeviceWriter{
public void saveToDevice();
}
public void BusinessObject{
private IDeviceWriter writer;
public void setDeviceWriter(IDeviceWriter writer){
this.writer=writer;
}
public void save(){
writer.saveToDevice();
}
}
这样一来,BusinessObject就是可重用的,如果有存储至Floppy或USB的需求,只有继承IDeviceWriter即可,而不用修改BusinessObject:
public class FloppyWriter implement IDeviceWriter{
public void saveToDevice(){
//实际存储至Floppy的方法
}
}
public class UsbDiskWriter implement IDeviceWriter{
public void saveToDevice(){
//实际存储至USB的方法
}
}
从这个角度看,Dependency Inversion的意思即是方法不依赖于实现,而是方法与实现都要依赖于抽象。
IoC的Control是控制的意思,其背后的意义也是一种依赖关系的转移,如果A依赖于B,其意义即是B拥有控制权,想要转移这种关系,所以依赖关系的反转即是控制关系的反转,由控制关系的转移;可以获得组件的可重用性,在上面的Java方法中,整个控制权从实际的FloppyWriter转移至抽象的接口IDeviceWriter上,使得BusinessObject、FloppyWriter、UsbDiskWriter这几个实现依赖于抽象的IDeviceWriter接口。
方法的业务逻辑部分应该是可以重用的,不应受到所使用框架或容器的影响,因为可能转移整个业务逻辑到其他的框架或容器,如果业务逻辑过于依赖容器,则转移到其他的框架或容器时,就会发生困难。
IoC在容器的角度,可以用一句话来说明——“Don't call me, I'll call you”,就是“不要向容器要求所需要的(对象)资源,容器会自动将这些对象给你!”。IoC要求的是容器不侵入方法本身,而方法本身提供好接口,容器可以透过这些接口将所需的资源注入至方法中,方法不向容器主动要求资源,所以不会依赖于容器的组件,方法本身不会意识到正被容器使用,可以随时从容器中脱离转移而不用作任何的修改,而这个特性正是一些业务逻辑中间件最需要的。