本文用来讲述设计模式中的依赖倒置原则。通过介绍,场景,代码推进的方式进行一步步的讲解。
一、介绍 依赖倒置原则(Dependence Inversion Principle)是指:
高层模块不应该依赖底层模块,二者都应该依赖其抽象。这里调用者就是上层,被调用者就是下层。
抽象不应该依赖细节,细节应该依赖抽象。(抽象是指接口或者抽象类,细节是指具体实现及使用)
依赖倒置的中心思想是面向接口编程 。
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
二、场景介绍 实现场景,一个人通过不同的通讯方式进行通信。例如,可以通过邮箱,通过微信,通过手机等等方式进行通信。
三、代码实现 3.1 实现代码 假设现在我们需要通过微信,短信,邮箱进行信息的传递。代码都较为简单,请先直接看代码,再进行代码的讲解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class DependenceInversionPrinciple { public static void main (String[] args) { Person person = new Person(); person.getInfoByEmail(new Email()); person.getInfoByMessage(new Message()); person.getInfoByWeChat(new WeChat()); } } class Person { public void getInfoByMessage (Message message) { message.getInfo(); } public void getInfoByEmail (Email email) { email.getInfo(); } public void getInfoByWeChat (WeChat weChat) { weChat.getInfo(); } } class Message { public void getInfo () { System.out.println("通过 短信 传递信息" ); } } class Email { public void getInfo () { System.out.println("通过 邮箱 传递信息" ); } } class WeChat { public void getInfo () { System.out.println("通过 微信 传递信息" ); } }
输出:
1 2 3 通过 邮箱 传递信息 通过 短信 传递信息 通过 微信 传递信息
通过上面的代码,我们实现了上面的场景描述。同时下图给出了类之间的关系。
从图中可以看到,Person类依赖WeChat,Message,Email类。
如果我们的需求不变,这样写代码是没有什么问题的。但是如果有一天,我们的需求扩大了,不再满足上面的三种通信方式。此时,我还需要使用钉钉和QQ进行通行呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class DependenceInversionPrinciple { public static void main (String[] args) { Person person = new Person(); person.getInfoByEmail(new Email()); person.getInfoByMessage(new Message()); person.getInfoByWeChat(new WeChat()); person.getInfoByWeChat(new QQ()); person.getInfoByWeChat(new DingDing()); } } class Person { public void getInfoByMessage (Message message) { message.getInfo(); } public void getInfoByEmail (Email email) { email.getInfo(); } public void getInfoByWeChat (WeChat weChat) { weChat.getInfo(); } public void getInfoByDingDing (DingDing ding) { weChat.getInfo(); } public void getInfoByWeChat (QQ qq) { weChat.getInfo(); } } class Message { public void getInfo () { System.out.println("通过 短信 传递信息" ); } } class Email { public void getInfo () { System.out.println("通过 邮箱 传递信息" ); } } class WeChat { public void getInfo () { System.out.println("通过 微信 传递信息" ); } class DingDing { public void getInfo () { System.out.println("通过 钉钉 传递信息" ); } class QQ { public void getInfo () { System.out.println("通过 QQ 传递信息" ); } }
如果按照上面的方式,我们是不是需要新增钉钉和QQ类,同时需要在Person类中去新增方法,那么这里就违反了设计模式中的开闭原则 ,当我们改动服务端代码时,客户端代码也不得不改动。而我们增加需求所要做到的就是,就在客户端不需要进行结构上的变化,达到对其功能的扩充。不然,以后是不是每新增一个需求就需要把客户端的代码拿来进行大刀阔斧一番?
从上图中,我们看到上层模块(Person)依赖下层模块(WeChat,Message,Email)。如果我们在其中开辟一个中间层,让上层Person,无法感知到下层的存在,是不是要好些呢?这样说有些抽象,我们继续看代码改动。
3.2 改进代码 现在对3.1中的代码进行改进。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class DependenceInversionPrinciple { public static void main (String[] args) { IReceiver message = new Message(); IReceiver WeChat = new WeChat(); IReceiver Email = new Email(); Person person = new Person(); person.getInfoByMessage(message); person.getInfoByMessage(WeChat); person.getInfoByMessage(Email); } } class Person { public void getInfoByMessage (IReceiver iReceiver) { iReceiver.getInfo(); } } interface IReceiver { void getInfo () ; } class Message implements IReceiver { public void getInfo () { System.out.println("通过 短信 传递信息" ); } } class Email implements IReceiver { public void getInfo () { System.out.println("通过 邮箱 传递信息" ); } } class WeChat implements IReceiver { public void getInfo () { System.out.println("通过 微信 传递信息" ); } }
输出:
1 2 3 通过 短信 传递信息 通过 微信 传递信息 通过 邮箱 传递信息
继续看此时类之间的关系图:
我们看到Person从过去直接依赖WeChat、Message、Email现在只依赖IReceiver,它根本就感知不到其下层的模块变化。
如果,此时我们再新增钉钉,QQ的通信方式,只需要让这两个类去实现IReceiver接口,而Person类根本就不需要改动,就能实现新增的通信方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class DependenceInversionPrinciple { public static void main (String[] args) { IReceiver message = new Message(); IReceiver weChat = new WeChat(); IReceiver email = new Email(); IReceiver qq = new QQ(); IReceiver ding = new DingDing(); Person person = new Person(); person.getInfoByMessage(message); person.getInfoByMessage(weChat); person.getInfoByMessage(email); person.getInfoByMessage(qq); person.getInfoByMessage(ding); } } class Person { public void getInfoByMessage (IReceiver iReceiver) { iReceiver.getInfo(); } } interface IReceiver { void getInfo () ; } class Message implements IReceiver { public void getInfo () { System.out.println("通过 短信 传递信息" ); } } class Email implements IReceiver { public void getInfo () { System.out.println("通过 邮箱 传递信息" ); } } class WeChat implements IReceiver { public void getInfo () { System.out.println("通过 微信 传递信息" ); } } class DingDing implements IReceiver { @Override public void getInfo () { System.out.println("通过 钉钉 传递信息" ); } } class QQ implements IReceiver { @Override public void getInfo () { System.out.println("通过 QQ 传递信息" ); } } =====输出====== 通过 短信 传递信息 通过 微信 传递信息 通过 邮箱 传递信息 通过 QQ 传递信息 通过 钉钉 传递信息
我们服务端对需求的扩展根本就没有影响到客户端代码(Person),这里的Person根本就没有增删代码。实现了上层和下层模块的解耦.这里的上层Person根本就没有感知到下层模块WeChat,…,QQ等。
四、总结 现在我们再来看一看依赖倒置的定义:
现在再看是不是高层模块(Person)和底层模块(WeChat,..,QQ)都依赖其抽象,而不是直接的高层模块依赖其底层模块。这里细节是具体实现,它依赖着抽象(接口)。
核心是面向接口面层,这里的高层模块依赖的就是IRecevier接口。
如果违反了该原则,下层模块改动就要带动上层模块一起跟着动(3.1节中的实例代码)。
使用及注意事项:
低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.即子类上层应具有抽象类或接口
变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
继承时遵循里氏替换原则。
依赖倒置体现在:在代码一中,是不是Person都依赖着WeChat,…,Email,箭头向下的;经过代码的优化处理,采取依赖倒置原则,是不是原来的箭头向下,即下层模块被依赖,而现在全都去实现其上层接口,即依赖着其接口,箭头向上反转了,所以依赖倒置由此而得名 。