设计模式之禅——六大设计原则之依赖倒置原则

时间:2022-10-01 22:44:28

依赖:假设A类的变化引起了B类的变化,则B依赖A类

依赖倒置原则
英文名:Dependence Inversion Priceple,简称DIP
定义:

  • 高层模块不应该依赖底层模块,两者都应该依赖其抽象
  • 抽象不应该依赖细节
  • 细节应该依赖抽象

1、每一个逻辑的实现都是逻辑的实现都是由原子逻辑组成的,不可分割的原则逻辑就是底层模块,原则逻辑的再组装就是高层模块。

2、在Java中,抽象就是接口或抽象类,两者都是不能直接被实例化的

3、细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是直接被实例化,也就是new xx()。

依赖倒置原则在Java语言中的变现:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
  • 接口或抽象类不依赖于实现类
  • 实现类依赖接口或抽象类

更加精简的定义就是“面向接口编程”—-OOD(Object-Oriented Design,面向对象设计)的精髓之一。

下面举一个没有实现依赖倒置原则的司机开车的实例

public class Driver{
public void drive(Benz benz){
benz.run();
}
}
----------
public class Benz{
public void run(){
System.out.println("奔驰开始跑了");
}
}


----------
public class client{
public static void main(String[] args){
Driver tom = new Driver;
Benz benz = new Benz();
tom.drive(benz);]
}
}

如果这里tom要去开宝马的车,就需要去修改代码。原因是司机和奔驰是紧耦合的关系(司机类依赖于Benz的具体实现类),结果导致系统的可维护性降低。

依赖倒置原则还可以减少并行开发的风险,什么是并行开发的风险?并行的最大风险就是风险扩散,本来只是一个程序的错误,逐步波及一个功能,模块,甚至整个项目。

ok,我们现在根据依赖倒置原则重新设计代码。

public interface IDriver{
public void drive(Icar car);
}
----------
public class Driver implements IDriver{
public void drive(Icar car){
car.run();
}
}
----------
public interface ICar{
public void run();
}
----------
public class Benz implements Icar{
public void run(){
System.out.println("奔驰开跑");
}
}
----------
public class BWM implements Icar{
public void run()
System.out.println("宝马开跑");
}
}
----------
public class Client{
public static void main(String[] args){
IDriver tom = new Driver();
Icar bwm = new BWM();
tom.drive(bwm);
}
}

开发过程中,A开发司机类,B开发汽车类,二者进度万一有偏差怎么办?A如果比B快很多难道也要一直等着B吗?其实,我们只需要制定出二者的接口就可以独立的运行,而且项目之间的单元测试也可以独立的运行,而TDD(Test-Driven Development,测试驱动开发)并发模式就是依赖倒置原则的*应用。
下面借助JMock工具,其最基本的功能是根据抽象虚拟一个对象进行测试,代码如下:

/*下面有关于注解的使用,可以去看我Thinking in Java--第二十章-注解
*定不负君望
*/
public interface IDriver{
public void drive(Icar car);
}
----------
public interface ICar{
public void run();
}
----------
//应该是版本问题,有bug,有兴趣的可以再研究一下
package com.sdkd.hms12;

import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;

import junit.framework.TestCase;

public class DriverTest extends TestCase{
Mockery context = new JUnit4Mockery();
@Test
public void testDriver(){
final ICar car = context.mock(ICar.class);
IDriver driver = new Driver();
context.checking(new Expectations(){
{
oneOf(car).run();
}
});
driver.drive(car);
}
}

依赖的三种写法:
1、构造函数传递依赖对象
2、Setter依赖注入
3、接口声明依赖对象

如何实现依赖倒置原则:

  • 每个类都尽量都有接口或抽象类,或者抽象类和接口都具备
  • 变量的表面类型尽量是接口或抽象类
  • 类尽量不要从具体类派生(不要超过两层)
  • 尽量不要复写基类的方法(基类方法可能有依赖倒置,修改之后可能会导致难以预料的结果
  • 结合里氏替换原则(父类能用的地方子类也可以用,详细见-里氏替换原则)

    然而,并不是一直都不依赖细节,法律就必须要有细节的定义。用设计模式也要”审时度势“。