六大设计原则(三)DIP依赖倒置原则
程序员文章站
2022-11-27 15:38:48
依赖倒置原则DIP(Dependence Inversion Principle) 依赖倒置原则的含义 高层模块不能依赖低层模块,二者都应该依赖其抽象。 抽象不应该依赖于细节。 细节应该依赖抽象。 什么是 高层模块?低层模块 ? 每一个原子逻辑就是低层模块,原子逻辑再组就是高层模块。 什么是 抽象和 ......
依赖倒置原则dip(dependence inversion principle)
依赖倒置原则的含义
- 高层模块不能依赖低层模块,二者都应该依赖其抽象。
- 抽象不应该依赖于细节。
- 细节应该依赖抽象。
什么是高层模块?低层模块?
每一个原子逻辑就是低层模块,原子逻辑再组就是高层模块。
什么是抽象和细节?
抽象是抽象类,不可被实例化。
细节是实现类,比如实现的接口或继承抽象类的子类,可以被实例化。
表现在java语言中就是面向接口编程
- 模块间的依赖是通过抽象来实现的,具体的实现类之间不能发生直接的依赖。
- 接口或抽象类不能依赖与实现类。
- 实现类依赖接口或抽象类。
***
我们假设有三个类,一个为场景类,一个为司机类,一个为奔驰类。通过这三个类我们便可以实现司机开动汽车这个场景。如图
具体的实现代码如下
司机类
package des.dip; //司机类 public class driver { //司机驾驶车 紧耦合 public void drive(benze benze){ benze.run(); } //司机驾驶宝马车 紧耦合 public void drive(bmw bmw){ bmw.run(); } }
奔驰类
package des.dip; //奔驰车 public class benze { public void run(){ system.out.print("奔驰车开始运行..."); } }
场景类
package des.dip; //场景类 public class client { public static void main(string[] args){ //创建一个司机 driver zs = new driver(); //创建一个奔驰车 benze benze = new benze(); //司机可以开奔驰车 zs.drive(benze); //假设此时增加一个宝马车呢?还要再增加一个方法,并且重新创建 //一个还好若是很多呢?难道要在司机类声明很多方法吗? bmw bmw = new bmw(); } }
package des.dip; //宝马车 public class bmw { //宝马车当然也可以开动 public void run(){ system.out.print("宝马车开动..."); } }
程序正常的写法就是如此,但是如果我们考虑下面一个问题,司机并不是只会开着一辆benze牌的车,假如我们再假如一个bmw(宝马)牌的车,我们传统的做法就是再新建一个类,然后再司机类中再添加一个drive bmw的方法。假如我们要添加无数品牌的汽车呢,难道还要再司机类中添加无数的drive方法吗?他们都有着相同的方法名,只是传入的汽车型号不同。
显然,传统的drive方法的写法,具有紧耦合性,只要车型变更,就不能再使用了。其导致的结果就是系统的可维护性大大降低,可读性也大大降低。
*
解决方法
使用依赖倒置原则**
dip第一种方法 接口注入法
建立两个接口,idriver和icar
此时业务的场景类就可以改写成如下
package des.dip; public class client1 { public static void main(string[] args){ //创建一个司机 /** * 此处明确两个概念: * idriver 叫做表面类型, driver1 叫做实际类型 或称抽象类型和实际类型 * * 此后所有的操作均是对抽象接口的操作,具体屏蔽了细节 */ idriver ds = new driver1(); icar c = new bmw1(); ds.drive(c); } }
表面类型和实际类型: idriver 叫做表面类型, driver1 叫做实际类型 或称抽象类型和实际类型
下面是接口类和实现类参考代码:
package des.dip; //司机接口 public interface idriver { //司机可以驾驶汽车,什么汽车不用管即抽象类(松耦合) public void drive(icar car); }
package des.dip; //抽象汽车类 public interface icar { //汽车启动 public void run(); }
package des.dip; public class driver1 implements idriver { @override public void drive(icar car) { car.run(); } }
package des.dip; public class bmw1 implements icar { @override public void run() { system.out.print("宝马车开始运行..."); } }
package des.dip; public class benze1 implements icar { @override public void run() { system.out.print("奔驰车开始运行..."); } }
假设我们项目中有两个类是依赖关系,此时我们只需要定义两个抽象类就可以独立开发了。
dip第二种方法 构造函数传递依赖对象
package des.dip; //司机接口 public interface idriver { //司机可以驾驶汽车,什么汽车不用管即抽象类(松耦合) public void drive(icar car); /***************************/ public void drive(); }
package des.dip; public class driver1 implements idriver { /******************************************************/ private icar car; //构造函数注入 public driver1(icar _car){ this.car = _car; } @override public void drive() { this.car.run(); } /******************************************************/ @override public void drive(icar car) { car.run(); } }
idriver ds1 = new driver1(new bmw1()); ds.run();
运行结果
构造函数依赖注入理解图示
dip第三种方法 setter方法传递依赖对象
代码参考
package des.dip; //司机接口 public interface idriver { public void setcar(icar car); public void drive(); }
package des.dip; public class driver1 implements idriver { /******************************************************/ private icar car; @override public void setcar(icar car) { this.car.run(); } @override public void drive() { this.car.run(); } }
package des.dip; public class client1 { public static void main(string[] args){ idriver ds1 = new driver1(); ds1.setcar(new bmw1()); ds1.drive(); } }
dip总结
- dip本质就是通过抽象类来实现彼此独立,互不影响
- 依赖倒置的核心是面向接口编程,即上面的第一种方法。
- 依赖倒置的具体使用规则如下
- 每个类尽量有接口或抽象类,或者二者都有。
- 变量的表面类型尽量是接口或抽象类。
- 任何类不应该从具体类派生。
- 尽量不要覆写基类的方法。
- 结合里氏替换原则进行。
- 依赖倒置需要审时度势,而不是永远抓住这个原则不放,任何一个原则的优点都是有限的。
对于倒置的理解
从反面讲:什么是正置?如上例子,我们开什么型号的车,就依赖什么样型号的车。不存在什么抽象类与接口,直接单独建立即可,需要什么建立什么。但是依赖倒置?就是对车进行抽象,抽象出类和接口,建立抽象间的依赖。