【设计模式笔记04】:依赖倒置原则-结合代码实例
摘要:依赖倒置原则(DIP)要求高层模块不应依赖低层模块,都该依赖抽象。通过消息接收案例展示了违反DIP的问题:Person类直接依赖Email类,导致扩展困难。重构后引入IReceivedMsg接口,演示了三种依赖注入方式(接口、构造器、Setter),使系统更灵活。结合UML类图,说明该方案同时满足开闭原则和里氏替换原则。最后以图表系统为例,展示如何通过抽象类和依赖注入实现可扩展性,强调配置修
·
接续上一篇的笔记:【设计模式笔记03】:里氏代换原则和依赖倒置原则
四、 依赖倒置原则 (DIP) (续)
5. 代码示例:消息接收
-
反面示例 (不遵循DIP)
- 场景:
Person类需要接收并处理来自Email类的信息。 - 代码实现:
class Email { public String getInfo() { return "电子邮件信息: Hello, world."; } } class Person { public void receiveEmail(Email email) { // 参数类型是具体的Email类 System.out.println(email.getInfo()); } } - 问题分析:
Person类的receiveEmail方法直接依赖了具体的Email类。- 如果现在需要接收微信(
Weixin)、短信(SMS)等其他类型的信息,就必须在Person类中增加新的方法,如receiveWeixin(Weixin weixin)。 - 每次增加新的信息类型,都需要修改
Person类的源代码,这违反了开闭原则 (OCP) 和依赖倒置原则 (DIP)。
- 场景:
-
正面示例 (遵循DIP)
- 重构思路: 引入一个抽象的
IReceivedMsg接口,让Person类依赖此接口,而不是具体的实现类。 - 依赖注入的三种方式:
-
接口依赖 (方法参数传递)
- 代码实现:
// 1. 定义抽象信息接口 interface IReceivedMsg { public String getInfo(); } // 2. Person类依赖接口 class Person { public void receive(IReceivedMsg iReceivedMsg) { // 参数类型是抽象的接口 System.out.println(iReceivedMsg.getInfo()); } } // 3. 具体信息类实现接口 class Email implements IReceivedMsg { public String getInfo() { return "电子邮件: Hello, world."; } } class Weixin implements IReceivedMsg { public String getInfo() { return "微信: How are you?"; } } - 优点: 代码依赖于抽象,当需要扩展支持新的消息类型(如短信)时,只需增加一个新的类实现
IReceivedMsg接口即可,Person类无需任何修改。
- 代码实现:
-
构造器依赖 (构造函数传递)
- 代码实现:
interface IMessage { public String getInfo(); } class People { private IMessage iMessage; // 成员变量是接口类型 (关联) public People(IMessage iMessage) { // 通过构造器注入依赖 this.iMessage = iMessage; } public void receive() { System.out.println(this.iMessage.getInfo()); } } class QQMessage implements IMessage { ... }
- 代码实现:
-
Setter依赖 (Setter方法传递)
- 代码实现:
interface IMessage { public String getInfo(); } class People { private IMessage iMessage; // 成员变量是接口类型 (关联) public void setIMessage(IMessage iMessage) { // 通过Setter方法注入依赖 this.iMessage = iMessage; } public void receive() { System.out.println(this.iMessage.getInfo()); } } class QQMessage implements IMessage { ... }
- 代码实现:
-
- 重构思路: 引入一个抽象的
-
UML类图示例:

UML类图展示了
Person类(或People类)与IMessage接口的关联关系,以及多个具体消息类(如Email,Weixin,QQMessage)对IMessage接口的实现关系。 -
原则的协同体现:
- 新增接口的实现类,无需修改已有类,符合开闭原则 (OCP)。
- 如果将接口用抽象类代替,此时,子类可以替换父类(抽象类)被
Person类使用,还将符合里氏代换原则 (LSP)。
6. 图表示例回顾与分析
我们再回头看之前开闭原则中的图表示例。

重构后的CRM图表示例的UML类图。
ChartDisplay类聚合AbstractChart抽象类,PieChart和BarChart继承自AbstractChart。
-
如何体现依赖倒置原则和里氏代换原则?
- 引入抽象图表类
AbstractChart:ChartDisplay类不再依赖具体的PieChart或BarChart,而是针对抽象的AbstractChart类进行编程。这就是依赖倒置原则的体现。 - 通过
setChart方法注入:客户端可以通过setChart(AbstractChart chart)方法将实例化的具体图表对象(如new PieChart())设置到ChartDisplay中。 - 扩展性:当需要增加一种新的图表,如折线图
LineChart时,我们只需要:- 创建一个
LineChart类,并让它继承AbstractChart。 - 在客户端代码中,向
ChartDisplay对象注入一个LineChart实例即可。 - 这个过程中,原有的
ChartDisplay、AbstractChart、PieChart、BarChart的源代码都无须修改。
- 创建一个
- LSP的体现:在调用
setChart方法时,任何AbstractChart的子类(如PieChart、BarChart、LineChart)都可以作为参数传入,并且ChartDisplay的行为是正确的、符合预期的。这就是里氏代换原则的体现。
- 引入抽象图表类
-
关于配置文件与源码修改
- 在实际软件开发中,具体使用哪个实现类,通常是通过XML、Properties等配置文件来决定的。
- 因为配置文件是纯文本文件,可以通过编辑器直接编辑,且无须重新编译。因此,一般不把对配置文件的修改认为是-对系统源代码的修改。
- 如果一个系统在扩展时只涉及到修改配置文件,而原有的程序代码没有任何修改,则可以认为是一个符合开闭原则的系统。
更多推荐

所有评论(0)