什么是依赖倒转原则
依赖倒转(Dependence Inversion Principle ):是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
1.抽象不应该依赖于细节,细节应该依赖于抽象。
2.高层模块不依赖底层模块,两者都依赖抽象。
我们举个例子:电脑有不同的组件,硬盘,内存,主板。
硬盘抽象类
1 //硬盘抽象类 2 public abstract class HardDisk { 3 public abstract void doSomething(); 4 }
具体硬盘(希捷硬盘)
1 //希捷硬盘 2 public class XiJieHardDisk extends HardDisk { 3 4 public void doSomething() { 5 System.out.println("希捷硬盘"); 6 } 7 8 }
具体硬盘(西数硬盘)
1 public class XiShuHardDisk extends HardDisk { 2 3 public void doSomething() { 4 System.out.println("西数硬盘"); 5 } 6 7 }
主板抽象类
1 //主板抽象类 2 public abstract class MainBoard { 3 public abstract void doSomething(); 4 }
具体主板(华硕主板)
1 public class HuaShuoMainBoard extends MainBoard{ 2 3 public void doSomething() { 4 System.out.println("华硕主板"); 5 } 6 7 }
具体主板(微星主板)
1 public class WeiXingMainBoard extends MainBoard { 2 3 public void doSomething() { 4 System.out.println("微星主板"); 5 } 6 7 }
内存抽象类
1 //内存抽象类 2 public abstract class Memory { 3 public abstract void doSomething(); 4 }
具体内存(金士顿内存)
1 public class JinShiDunMemory extends Memory { 2 3 public void doSomething() { 4 System.out.println("金士顿内存"); 5 } 6 7 }
具体内存(三星内存)
1 public class SanxingMemory extends Memory { 2 3 public void doSomething() { 4 System.out.println("三星内存"); 5 } 6 7 }
现在,电脑的各个零部件都有了,只差电脑了。首先,我们不按照依赖倒转原则,按照传统模式
传统的过程式设计倾向于使高层次的模块依赖于低层次的模块,抽象层依赖于具体的层次。
这样,电脑应该是这样的
1 //电脑 2 public class Computer{ 3 private HuaShuoMainBoard huaShuoMainBoard; 4 private JinShiDunMemory jinShiDunMemory; 5 private XiJieHardDisk xiJieHardDisk; 6 7 public HuaShuoMainBoard getHuaShuoMainBoard() { 8 return huaShuoMainBoard; 9 } 10 public void setHuaShuoMainBoard(HuaShuoMainBoard huaShuoMainBoard) { 11 this.huaShuoMainBoard = huaShuoMainBoard; 12 } 13 public JinShiDunMemory getJinShiDunMemory() { 14 return jinShiDunMemory; 15 } 16 public void setJinShiDunMemory(JinShiDunMemory jinShiDunMemory) { 17 this.jinShiDunMemory = jinShiDunMemory; 18 } 19 public XiJieHardDisk getXiJieHardDisk() { 20 return xiJieHardDisk; 21 } 22 public void setXiJieHardDisk(XiJieHardDisk xiJieHardDisk) { 23 this.xiJieHardDisk = xiJieHardDisk; 24 } 25 }
这时,要组装一台电脑
public class MainClass { public static void main(String[] args) { Computer computer = new Computer(); computer.setHuaShuoMainBoard(new HuaSuoMainBoard()); computer.setJinShiDunMemory(new JinShiDunMemory()); computer.setXiJieHardDisk(new XiJieHardDisk()); computer.setHuaShuoMainBoard(new WeiXingMainBoard());//报错,无法安装 } }
可以看到,这种情况下,这台电脑就只能安装华硕主板,金士顿内存和希捷硬盘了,这对用户肯定是不友好的,用户有了机箱肯定是想按照自己的喜好,选择自己喜欢的配件。
电脑就是高层业务逻辑,主板,内存,硬盘就是中层模块,还有更低的底层模块我们没有写那么细,但都是一个意思,这样的方式显然是不可取的。
下面,我们改造一下,让Computer依赖接口或抽象类,下面的模块同样如此
Computer
1 public class Computer { 2 private MainBoard mainBoard; 3 private Memory memory; 4 private HardDisk harddisk; 5 6 public MainBoard getMainBoard() { 7 return mainBoard; 8 } 9 10 public void setMainBoard(MainBoard mainBoard) { 11 this.mainBoard = mainBoard; 12 } 13 14 public Memory getMemory() { 15 return memory; 16 } 17 18 public void setMemory(Memory memory) { 19 this.memory = memory; 20 } 21 22 public HardDisk getHarddisk() { 23 return harddisk; 24 } 25 26 public void setHarddisk(HardDisk harddisk) { 27 this.harddisk = harddisk; 28 } 29 }
这时,再组装
1 public class MainClass { 2 public static void main(String[] args) { 3 Computer computer = new Computer(); 4 computer.setMainBoard(new HuaSuoMainBoard()); 5 computer.setMemory(new JinShiDunMemory()); 6 computer.setHarddisk(new XiJieHardDisk()); 7 8 computer.setMainBoard(new WeiXingMainBoard());//完全没有问题 9 } 10 }
这样,用户就可以根据自己的喜好来选择自己喜欢的品牌,组装电脑了。
为什么要采取依赖倒转这种方式
面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。
面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。
依赖倒转模式应用实例
1.工厂方法模式
2.模板方法模式
3.迭代模式
综上所诉,我们可以看出一个应用中的重要策略决定及业务模型正是在这些高层的模块中。也正是这些模型包含着应用的特性。但是,当这些模块依赖于低层模块时,低层模块的修改将会直接影响到它们,迫使它们也去改变。这种境况是荒谬的。应该是处于高层的模块去迫使那些低层的模块发生改
变。应该是处于高层的模块优先于低层的模块。无论如何高层的模块也不应依赖于低层的模块。而且,我们想能够复用的是高层的模块。通过子程序库的形式,我们已经可以很好地复用低层的模块了。当高层的模块依赖于低层的模块时,这些高层模块就很难在不同的环境中复用。但是,当那些高层模块独
立于低层模块时,它们就能很简单地被复用了。这正是位于框架设计的最核心之处的原则。