设置模式原则(3)依赖倒置原则

定义

说起依赖倒置原则(DIP,Depencence Inversion Principle)就想起了控制反转(IOC,Inversion of Control),在好几年前本科学习阶段接触Spring时就开始知道了相关皮毛概念。在后续的开发中也是大量采用IOC。
DIP原始定义如下:
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
大概意思如下:
1、高层模块不应该依赖于底层模块,两者都应该依赖于抽象。
2、抽象不应该依赖于细节,而应该是细节依赖于抽象。

在业务模块组成中,不可分割的原子逻辑就是底层模块。由原子逻辑进一步组装而成的就是高层模块。在Java中,我们可以把抽象类和接口称之为抽象,而针对抽象进行继承、实现、进一步具体化的类称之为细节。

综上,进一步针对Java开发中的具体化定义DIP便是
1、模块间依赖关系应该通过接口或抽象类产生
2、接口或抽象类不应该依赖于实现类,而应该是实现类依赖于接口或抽象

简而言之就是面向接口编程/设计(OOD,Object-Oriented Design)

优点

1、减少类间耦合
2、降低并行开发风险
3、提高系统稳定性、代码可读性和可维护性。

代码示例:(人喂食动物)

abstract class Animal {
     public abstract void eat();
}
class Cat extends Animal {
     @Override
     public void eat() {
          System.out.println("Cat eating something");
     }
}
class Dog extends Animal {
     @Override
     public void eat() {
          System.out.println("Dog eating bone..");
     }
}
interface IPerson{
     public void feed(Animal animal);     
}
class Person  implements IPerson {{    
     //using polymorphic to get a high efficient way in programming
     public void feed(Animal animal) {
          animal.eat();
     }
}
class Client{
      public static void main(String[] args) {
              IPerson p=new Person();
              p.feed(new Cat());
              p.feed(new Dog());
          }
}

从上面代码可以知道,人类喂食动物体现在代码上只需要拿到动物的共同抽象类Animal传参就ok了,而不是在feed中进行具体实现类的传入。依靠多态的特性,在新增加动物时也不需要修改Person类的代码,很大程度上减少了代码变更风险的扩散。另一方面运用接口IPerson限定了Person的动作,这类似于一种无形的契约。
在以上代码结构中,抽象类Animal、接口IPerson是抽象,相应的实现类是细节;Client是高层模块。在Client中借用IPerson调用具体的实现类方法,IPerson作为表面类型,通过实际类型Person的赋值进而实现相关需求;在高层业务模块及具体实现细节中有都依赖于抽象接口。在这种代码结构下,如果需要增加动物种类,就比如现在新添加了猴子Monkey需要人去喂食,那只需要让Monkey继承Animal,而后修改Client模块代码即可,在此可没有对Person代码进行修改哦。如此减少了类间耦合,大大提高了系统稳定性。

另外一方面是关于并行开发
在软件工程开发思想的引导下,团队合作的重要性不言而喻,上世纪小作坊下的个人英雄主义早已不适用新时代大型项目的开发;一个项目的完成往往是多人协作开发的结果。就拿上述例子展开,若不利用接口抽象。那很明显的可以看到Person类的开发需要依赖于各个Animal子类(注意在此之下已经没有了Animal抽象类),就比如“人喂食Cat”的功能开发测试得要等待Cat类的开发完成,这种情况下何谈并行开发,何谈协作?更不用谈软件工程开发思维了。
而如果我们使用依赖倒置的方法呢?利用接口之间的依赖就可以完成并行开发了。此时模块间单元测试也可以借助JMock工具( http://jmock.org/javadoc/jmock-2.6.0/doc/org/jmock/integration/junit4/JUnit4Mockery.html)进行。此时就可以进行测试驱动开发(TDD,Test-Driven Development),这种先写测试类后进行具体开发实现的方式非常适用于复杂的科研项目中。

依赖的写法

依赖是可以层层传递的,但只要是抽象传递就可以无所畏惧。依赖写法常用的有三种,主要采取的还是组合的方式
1、构造函数依赖注入

interface IPerson{
     public void feed();     
}
class Person implements IPerson {
      Animal animal;
      Person(Animal animal){
           this.animal=animal;
      }   
      public void feed() {
           animal.eat();
     }
}

2、Setter函数注入
也就是把传参赋值从构造函数放到了Setter函数中

interface IPerson{
     public void feed();
     public void setAnimal(Animal animal)}
class Person implements IPerson {
      Animal animal;
      public void setAnimal(Animal animal){
           this.animal=animal;
      }   
      public void feed() {
           animal.eat();
     }
}

3、接口声明声明依赖对象,就是上面最开始的案例代码了

interface IPerson{
     public void feed(Animal animal);     
}

以上三种方式,根据不同的业务场景选择适用的写法即可。

总结

DIP作为六大原则中最难以落地的一条,是实现开闭原则(OCP,对扩展开放;对修改关闭)的重要途径,根本在于面向接口编程(OOD)。
由此又衍生出了以下几条实践规则
1、每个类都尽量拥有接口或抽象类,或两者兼备
2、变量的表面类型尽量是接口或抽象类
3、若条件允许,任何类都不应该从具体类派生,尤其是项目处于开发状态中
4、尽量不要覆写基类方法,减少对依赖稳定性的影响
5、结合里氏替换原则

扫描二维码关注公众号,回复: 8768143 查看本文章
发布了130 篇原创文章 · 获赞 39 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34901049/article/details/104051118
今日推荐