写点什么

06. 依赖倒置原则介绍

作者:杨充
  • 2025-03-11
    湖北
  • 本文字数:5194 字

    阅读完需:约 17 分钟

06.依赖倒置原则介绍

目录介绍

  • 01.问题思考的分析

  • 02.学习依赖倒置目标

  • 03.理解依赖倒置原则

  • 04.依赖倒置原则思想

  • 05.多数据库操作案例

  • 06.用户购买家电案例

  • 07.发送消息的案例

  • 08.依赖倒置 VS 依赖注入

  • 09.依赖倒置的思考

  • 10.依赖倒置原则总结

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网


https://yccoding.com/


设计模式 Git 项目地址:https://github.com/yangchong211/YCDesignBlog


简介: 依赖倒置原则是面向对象设计六大原则之一,强调高层模块不应依赖低层模块,两者应依赖于抽象。通过依赖接口或抽象类而非具体实现,降低模块耦合度,提升系统灵活性和可维护性。本文详解该原则的概念、目标、思想及其实现方式(如依赖注入),并结合多数据库操作、用户购买家电、发送消息等实际案例,深入探讨其应用与优缺点。

01.问题思考的分析

依赖倒置原则。单一职责原则和开闭原则的原理比较简单,但是,想要在实践中用好却比较难。而今天要讲到的依赖倒置原则正好相反。


这个原则用起来比较简单,但概念理解起来比较难。比如,下面这几个问题,你看看能否清晰地回答出来:


  1. “依赖倒置”这个概念指的是“谁跟谁”的“什么依赖”被反转了?“倒置”两个字该如何理解?

  2. 经常听到另外一个概念:依赖注入。这两个概念跟“依赖倒置”有什么区别和联系呢?它们说的是同一个事情吗?

02.学习依赖倒置目标

  1. 搞懂依赖倒置原则,为什么依赖倒置是重要的,以及它如何帮助我们构建灵活、可扩展和可维护的软件系统。

  2. 掌握依赖倒置的实现方式,依赖注入、工厂模式、策略模式等常见的实现方式,以及它们的优缺点和适用场景。

  3. 应用依赖倒置到实际项目:通过实际项目的练习和实践,将依赖倒置应用到实际的软件开发中。

03.理解依赖倒置原则

依赖倒置原则是面向对象设计的六大原则之一,它的定义是:


  1. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。

  2. 抽象不应该依赖于细节。细节应该依赖于抽象。


换句话说,设计中应当依赖于接口或抽象类,而不是依赖于具体实现。这种设计有助于减少模块之间的耦合,增加系统的灵活性和可维护性。

04.依赖倒置原则思想

依赖倒置原则的核心思想是面向抽象编程而不是面向具体编程。通过依赖抽象层次(如接口、抽象类),可以让高层模块不依赖具体的实现细节,从而使得系统更具扩展性和灵活性。

05.多数据库操作案例

例子 1:违反依赖倒置原则。假设我们有一个 UserService 类,它直接依赖于 MySQLDatabase 类来进行数据操作。


class MySQLDatabase {    public void saveUser(String username) {        // 保存用户到MySQL数据库        System.out.println("Saving " + username + " to MySQL database.");    }}
class UserService { private MySQLDatabase database = new MySQLDatabase();
public void addUser(String username) { database.saveUser(username); }}
复制代码


在这个例子中,UserService 类直接依赖于 MySQLDatabase 类,这是对具体实现的依赖,违反了依赖倒置原则。


如果我们需要将数据库换成 PostgresSQLDatabase,或者换成 AliSQLDatabase,我们必须修改 UserService 类,这增加了系统的耦合度和修改的风险。


例子 2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。


// 抽象层:定义一个接口,供不同的数据库实现interface Database {    void saveUser(String username);}
// 具体实现:MySQL数据库class MySQLDatabase implements Database { public void saveUser(String username) { System.out.println("Saving " + username + " to MySQL database."); }}
// 具体实现:PostgreSQL数据库class PostgreSQLDatabase implements Database { public void saveUser(String username) { System.out.println("Saving " + username + " to PostgreSQL database."); }}
// 高层模块:UserService类依赖于抽象接口,而不是具体实现class UserService {
private Database database;
public UserService(Database database) { this.database = database; }
public void addUser(String username) { database.saveUser(username); }}
复制代码


在这个例子中,UserService 类依赖于 Database 接口,而不是具体的数据库实现。


这意味着如果我们想更换数据库,只需要传入不同的实现类,而不需要修改 UserService 类本身。这样做遵循了依赖倒置原则,降低了模块之间的耦合度。


从这个例子可知,定义数据库抽象层供不同的数据库实现,然后在 UserService 类依赖于抽象接口而不是具体实现,可以充分降低代码耦合度,可以快速拓展其他数据库实现。

06.用户购买家电案例

例子 1:违反依赖倒置原则。假设顾客,想要买冰箱,洗衣机,电视,或者其他电器,它直接依赖于 Customer 类来进行数据操作。


public class Customer {    public void buyFridge() {        System.out.println("购买冰箱");    }
public void buyTelevision() { System.out.println("购买电视"); }}
复制代码


例子 2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。


/** * 商品接口 */public interface IGood {    /**     * 购买商品     */    void buy();}
public class Customer { public void buy(IGood iGood) { iGood.buy(); }}
/** * 冰箱商品 */public class FridgeGood implements IGood { @Override public void buy() { System.out.println("购买冰箱"); }}
/** * 电视商品 */public class TelevisionGood implements IGood { @Override public void buy() { System.out.println("购买电视"); }}
/** * 洗衣机商品 */public class WashMachineGood implements IGood { @Override public void buy() { System.out.println("购买洗衣机"); }}
public class Main { public static void main(String[] args) { Customer customer = new Customer(); customer.buy(new FridgeGood()); customer.buy(new TelevisionGood()); customer.buy(new WashMachineGood()); }}
复制代码

07.发送消息的案例

例子 1:违反依赖倒置原则。假设用户,可以通过邮件,短信息,信件等发送消息,下面这种就不太友好。


public class Notification {    public void email(String cellphone, String message) {        System.out.println("通过邮件发送消息");    }
public void message(String cellphone, String message) { System.out.println("通过短信息发送消息"); } public void letter(String cellphone, String message) { System.out.println("通过信件发送消息"); }}
复制代码


例子 2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。


public class Notification {  private MessageSender messageSender;    public Notification(MessageSender messageSender) {    this.messageSender = messageSender;  }    public void sendMessage(String cellphone, String message) {    this.messageSender.send(cellphone, message);  }}
public interface MessageSender { void send(String cellphone, String message);}
// 邮件发送类public class EmailSender implements MessageSender { @Override public void send(String cellphone, String message) { //.... }}
// 短信发送类public class SmsSender implements MessageSender { @Override public void send(String cellphone, String message) { //.... }}
// 站内信发送类public class InboxSender implements MessageSender { @Override public void send(String cellphone, String message) { //.... }}
复制代码

08.依赖倒置 VS 依赖注入

8.1 理解依赖注入

‌依赖注入(Dependency Injection, DI)是一种设计模式,旨在将对象的依赖关系从对象内部转移到外部管理,从而降低类之间的耦合度,提高代码的可维护性和可测试性‌。‌

8.2 依赖注入方式

依赖注入(Dependency Injection,DI)是一种通过将依赖关系从高层模块解耦的方式,常用于实现依赖倒置原则。


  1. 构造函数注入:通过在类的构造函数中声明依赖参数,将依赖关系通过构造函数传递给类的实例。最常见的依赖注入方式。

  2. Setter 方法注入:通过提供一组 setter 方法,允许外部代码设置依赖对象。这种方式相对于构造函数注入更灵活,可以在对象创建后随时更改依赖。

  3. 接口注入:通过在类中定义一个接口,该接口包含用于注入依赖的方法。类实现该接口,并通过接口方法接收依赖对象。这种方式相对较少使用,因为它引入了更多的接口和方法。

  4. 属性注入:通过在类中声明依赖对象的属性,并提供相应的 setter 方法,将依赖对象注入到属性中。可能导致类的实例在没有依赖对象的情况下被创建。

8.3 两者有何区别

依赖倒置原则(Dependency Inversion Principle,DIP)和依赖注入(Dependency Injection,DI)是面向对象设计中两个相关但不同的概念。


  1. 依赖倒置原则是一种设计原则,它指导我们在设计软件时应该依赖于抽象而不是具体实现。高层模块不应该直接依赖于低层模块,而是通过抽象接口或基类来进行依赖。

  2. 依赖注入是一种实现依赖倒置原则的具体技术,它通过将依赖关系从高层模块解耦,将依赖对象注入到类的实例中。依赖注入有多种方式,如构造函数注入、setter 方法注入、属性注入等。它的目的是通过外部注入依赖对象,而不是在类内部创建或获取依赖对象。


依赖注入是实现依赖倒置原则的一种常见方式,但并不是唯一的方式。依赖倒置原则还可以通过工厂模式、策略模式等其他设计模式来实现。

09.依赖倒置的思考

9.1 依赖倒置优点

  1. 降低耦合性:高层模块和低层模块之间通过抽象进行解耦,避免了对具体实现的依赖。

  2. 增强可扩展性:通过依赖抽象,可以更容易地替换或扩展具体实现,而不需要修改高层模块。

  3. 提高可维护性:模块之间的解耦使得系统的维护变得更加容易,修改一个模块的实现不会影响到其他模块。

9.2 依赖倒置缺点

  1. 增加系统的复杂性:为了遵循依赖倒置原则,通常需要引入额外的抽象层次,这可能会增加系统的复杂性。

  2. 可能导致过度设计:在简单系统中,如果过度使用依赖倒置原则,可能导致不必要的复杂性,造成过度设计。

9.3 在设计模式的体现

  1. 工厂模式(Factory Pattern):通过定义抽象工厂接口,让具体的工厂类实现该接口,从而将对象的创建过程与使用过程解耦。这样,高层模块只依赖于抽象工厂接口,而不依赖于具体的产品类。这符合依赖倒置原则,使得高层模块依赖于抽象而非具体实现。

  2. 观察者模式(Observer Pattern):观察者依赖于被观察者的抽象接口,而不依赖于具体的被观察者。这样,当被观察者发生变化时,观察者可以接收到通知并做出相应的处理,而不需要直接依赖于具体的被观察者。

  3. 策略模式(Strategy Pattern):通过定义一个抽象策略接口,让具体的策略类实现该接口,从而将算法的选择与使用解耦。高层模块通过依赖于抽象策略接口,而不依赖于具体的策略类,实现了依赖倒置原则。


这些设计模式的共同点是,它们都通过引入抽象接口或基类,将高层模块与具体实现解耦,使得高层模块依赖于抽象而非具体实现。符合依赖倒置原则的设计思想。

10.依赖倒置原则总结

  1. 依赖倒置问题思考:“依赖倒置”这个概念指的是“谁跟谁”的“什么依赖”被反转了?“倒置”两个字该如何理解?

  2. 学习该原则目标:搞懂该原则作用,掌握实现方式,并且应用到实际案例项目中。

  3. 依赖倒置的定义:高层模块不应该依赖底层模块,应该依赖于抽象。换句话说,设计中应当依赖于接口或抽象类,而不是依赖于具体实现。

  4. 依赖倒置原则思想:通过依赖抽象层次(如接口、抽象类),可以让高层模块不依赖具体的实现细节,从而使得系统更具扩展性和灵活性。

  5. 多数据库操作案例:从这个例子可知,定义数据库抽象层供不同的数据库实现,然后在 UserService 类依赖于抽象接口而不是具体实现,可以充分降低代码耦合度,可以快速拓展其他数据库实现。

  6. 依赖注入如何理解:依赖注入(Dependency Injection,DI)是一种通过将依赖关系从高层模块解耦的方式,常用于实现依赖倒置原则。

  7. 依赖注入有哪些方式:1.构造函数注入;2.Setter 方法注入;3.接口注入;4.属性注入

  8. 依赖倒置和依赖注入区别:依赖倒置原则是一种设计原则,依赖注入是一种实现依赖倒置原则的具体技术。

  9. 依赖倒置有哪些缺点:为了遵循依赖倒置原则,通常需要引入额外的抽象层次,这可能会增加系统的复杂性。

  10. 在设计模式的体现有哪些:工厂模式,观察者模式,策略模式,这些设计模式的共同点是,它们都通过引入抽象接口或基类,将高层模块与具体实现解耦,使得高层模块依赖于抽象而非具体实现。符合依赖倒置原则的设计思想。

11.更多内容推荐


发布于: 刚刚阅读数: 4
用户头像

杨充

关注

还未添加个人签名 2018-07-30 加入

还未添加个人简介

评论

发布
暂无评论
06.依赖倒置原则介绍_杨充_InfoQ写作社区