设计模式七大原则——依赖倒转原则

一、基本介绍

  依赖倒转原则(Dependence Inversion Principle)是指:

  (1)高层模块不应该依赖低层模块,二者都应该依赖其抽象

  (2)抽象不应该依赖细节,细节应该依赖抽象

  (3)依赖倒转(倒置)的中心思想是面向接口编程

  (4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口(interface)或抽象类(abstract class),细节就是具体的实现类

  (5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成

二、应用实例

  编程完成Person接收消息功能

  具体代码如下:

 1 public class DependencyInversion {
 2     public static void main(String[] args) {
 3         Person person = new Person();
 4         person.receive(new Email());
 5     }
 6 }
 7 
 8 class Email {
 9     public String getInfo() {
10         return "电子邮件信息:Hello World!";
11     }
12 }
13 
14 //完成Person接收消息
15 class Person {
16     public void receive(Email email) {
17         System.out.println(email.getInfo());
18     }
19 }

  运行结果:

  

  分析:

    以上代码虽然完成了功能,但是Person类和Email类之间存在严重的耦合,如果我们接收信息的对象不是Email,而是微信,短信,则需要增加新的类,而且Person类中需要增加新的方法

  解决方案:

    引入一个接口IReceiver,表示接收者,这样Person类和IReceiver接口发生依赖,因为微信,短信等都属于接收的方式,所以实现IReceiver接口即可,这样就符合了依赖倒转原则

  具体代码实现:

 1 public class DependencyInversion {
 2     public static void main(String[] args) {
 3         Person person = new Person();
 4         person.receive(new Email());
 5         person.receive(new Wechat());
 6     }
 7 }
 8 
 9 //定义接口
10 interface IReceiver {
11     String getInfo();
12 }
13 
14 //电子邮件
15 class Email implements IReceiver {
16     @Override
17     public String getInfo() {
18         return "电子邮件信息:Hello World!";
19     }
20 }
21 
22 //微信
23 class Wechat implements IReceiver {
24     @Override
25     public String getInfo() {
26         return "微信信息:Hello World!";
27     }
28 }
29 
30 //完成Person接收消息
31 class Person {
32     public void receive(IReceiver iReceiver) {
33         System.out.println(iReceiver.getInfo());
34     }
35 }

  运行结果:

  

  这样写之后,每次如果新增接收消息的方式,直接实现IReceiver接口即可,同时将Person类中的receive方法的参数改为IReceiver接口,也避免了类与类之间的耦合

三、依赖关系传递的三种方式和应用案例

  三种方式:接口传递、构造方法传递、setter方式传递

  应用实例:编程实现打开电视机操作(重在理解思想)

  方式一:

  (1)接口传递

 1 /**
 2  * 通过接口传递实现依赖
 3  */
 4 public class InterfaceImplements {
 5     public static void main(String[] args) {
 6         ChangHongTV changHongTV = new ChangHongTV();
 7         OpenAndClose openAndClose = new OpenAndClose();
 8         openAndClose.open(changHongTV);
 9     }
10 }
11 
12 //开关的接口
13 interface IOpenAndClose {
14     void open(ITV itv);
15 }
16 
17 //TV的接口
18 interface ITV {
19     void play();
20 }
21 
22 //实现接口
23 class OpenAndClose implements IOpenAndClose {
24     @Override
25     public void open(ITV itv) {
26         itv.play();
27     }
28 }
29 
30 //用户自定义电视
31 class ChangHongTV implements ITV {
32     @Override
33     public void play() {
34         System.out.println("长虹电视机已开启!");
35     }
36 }

  运行结果:

  

   分析:首先定义了一个专门用于电视机开关操作的抽象接口IOpenAndClose,然后定义了一个用于描述电视机的抽象接口ITV,接着使用OpenAndClose方法实现IOpenAndClose接口,并且自定义一个电视机类实现ITV接口,在main方法中实例化用户自定义的电视机类并且实例化OpenAndClose类,使用OpenAndClose类中的open方法,将用户自定义的电视机类的对象作为参数传递进去

  答疑:

  Q:看到这里很多读者可能会疑惑,为什么搞这么麻烦,直接写个开关的类来调用电视机类不就好了吗?

  A:这样确实可以,但是代码不够成熟,可拓展性也不好,因为电视不是唯一的,开关也不是唯一的,拟定一个统一的接口就像制定一个标准一样,不管是什么电视什么开关,实现这个接口就必须实现其中的所有抽象方法,实现了这些方法也就遵循了这个标准,有了这两个接口,不管是任何电视任何开关都能适用,降低了各个类之间耦合度的同时也提升了代码的可拓展性

  方式二:

  (2)构造方法传递

 1 /**
 2  * 通过构造方法实现依赖传递
 3  * */
 4 public class ConstructionImplements {
 5     public static void main(String[] args) {
 6         OpenAndClose openAndClose = new OpenAndClose(new AppleTV());
 7         openAndClose.open();
 8     }
 9 }
10 
11 //开关的接口
12 interface IOpenAndClose{
13     void open();
14 }
15 
16 //TV的接口
17 interface ITV{
18     void play();
19 }
20 
21 //实现接口
22 class OpenAndClose implements IOpenAndClose{
23     private ITV itv;
24 
25     public OpenAndClose(ITV itv) {
26         this.itv = itv;
27     }
28 
29     @Override
30     public void open() {
31         this.itv.play();
32     }
33 }
34 
35 class AppleTV implements ITV{
36     @Override
37     public void play() {
38         System.out.println("苹果电视已开启!");
39     }
40 }

  运行结果:

  

   分析:开关和电视的接口定义与上面相同不再阐述,不过这里的OpenAndClose类换了另一种方法来调用TV接口,将其作为成员变量,使得OpenAndClose类在实例化的时候就传入TV接口,并在open方法中可以直接调用成员变量的play方法,降低了耦合度的同时提升了效率

  方式三:

  (3)setter方式传递

 1 /**
 2  * 通过setter方法实现依赖传递
 3  */
 4 public class SetterImplements {
 5     public static void main(String[] args) {
 6         OpenAndClose openAndClose = new OpenAndClose();
 7         openAndClose.setItv(new XiaomiTV());
 8         openAndClose.open();
 9     }
10 }
11 
12 //开关的接口
13 interface IOpenAndClose {
14     void open();
15 
16     void setItv(ITV itv);
17 }
18 
19 //TV的接口
20 interface ITV {
21     void play();
22 }
23 
24 //实现接口
25 class OpenAndClose implements IOpenAndClose {
26     private ITV itv;
27 
28     @Override
29     public void setItv(ITV itv) {
30         this.itv = itv;
31     }
32 
33     @Override
34     public void open() {
35         this.itv.play();
36     }
37 }
38 
39 class XiaomiTV implements ITV {
40     @Override
41     public void play() {
42         System.out.println("小米电视已开启!");
43     }
44 }

  运行结果:

  

   分析:电视的接口定义与上面相同不再阐述,开关接口的定义与上面不同在于,定义了setItv方法,并将TV的接口作为参数,这就意味着实现IOpenAndClose接口必须实现这两个方法,先传入一个TV接口,然后再调用通过该实现该TV接口的类调用open方法

四、注意事项和细节

  (1)低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。

  (2)变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。

  (3)继承时遵循里氏替换原则

posted @ 2021-01-23 22:14  孤云jh  阅读(874)  评论(0编辑  收藏  举报