依赖倒置,究竟什么被倒置了?

系统
本文通过一个电商示例分析了什么是依赖倒置原则,并且提出了依赖倒置的两种实现风格,通过引入抽象层,可以降低系统的耦合度,提升系统的扩展性和可维护性。

当我们需要某个类A中使用到另外一个类B时,最直接的方式就是在A中直接依赖B,但是,今天我们要讲解的主角却是反其道而行之,它就是依赖倒置原则,那么,什么是依赖倒置原则?这种反向思维可以带来什么收益?这篇文章就来聊一聊。

什么是依赖倒置?

依赖倒置原则,英文为:Dependency inversion principle(简称DIP),也是 Robert C. Martin提出的 SOLID原则中的一种,老规矩,还是先看看作者 Robert C. Martin 对接口依赖倒置原则是如何定义的:

The Dependency Inversion Principle (DIP) states that high-level
modules should not depend on low-level modules; both should 
depend on abstractions. Abstractions should not depend on details.
Details should depend upon abstractions.

通过作者对依赖倒置的定义,可以总结出其核心思想是:高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该取决于抽象。

直接依赖的问题

对于上述依赖倒置的定义,如何理解呢?我们先来看下传统这种直接依赖会存在什么问题?如下为一张直接依赖的关系图:

在上图中,高层组件 ObjectA直接依赖于低层组件 ObjectB,高层组件的重用机会受到限制,因为任何对低层组件的更改都会直接影响高层组件。

为了更好的说明直接依赖的问题,这里以一个真实的电商场景为例进行说明,其中有一个高层模块 OrderService用于处理订单,这个高层模块依赖于一个低层模块 OrderRepository来存储和检索订单数据。示例代码如下:

// 高层模块:OrderService
public class OrderService {
    private MySQLOrderRepository mySQLRepository;

    public OrderService(MySQLRepository mySQLRepository) {
        this.mySQLRepository = mySQLRepository;
    }

    public void createOrder(Order order) {
        // 一些业务逻辑
        mySQLRepository.save(order);
    }
}

// 低层模块:MySQLRepository
public class MySQLRepository {
    public void save(Order order) {
        // 使用 MySQL数据库保存订单
    }
}

在上述例子中,OrderService直接依赖于 OrderRepository,这种设计存在几个缺点:

  • 紧耦合:如果要把数据库从 MySQL切换到其他的数据库,我们需要修改 OrderService,因为它直接依赖于 OrderRepository。
  • 难以测试:在进行单元测试时,我们无法轻松地对 OrderService 进行模拟,因为它直接依赖于具体实现 MySQLRepository。
  • 重用性差:如果在另一个项目中我们需要使用 OrderService 但存储订单的方式不同,例如使用文件系统或远程服务,我们将无法直接重用 OrderService。

那么,对于这些缺点,该如何解决呢?接下来我们将重点讲解。

如何实现依赖倒置?

这里提供两种主流的解决方案。

方案一:引入抽象层

通过低级组件实现高级组件的接口,要求低级组件包依赖于高级组件进行编译,从而颠倒了传统的依赖关系,如下图:

图1中,高层对象A依赖于底层对象B的实现;图2中,把高层对象A对底层对象的需求抽象为一个接口A,底层对象B实现了接口A,这就是依赖反转。

因此,上面的问题我们也可以通过引入一个抽象层 OrderRepository来解耦高层模块和低层模块,整个关系图如下:

通过这种方式,OrderService依赖于 OrderRepository接口而不是具体实现 MySQLRepository。这样,我们可以轻松替换低层实现而无需修改高层模块,修改后的代码如下:

// 高层模块:OrderService
public class OrderService {
    private OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void placeOrder(Order order) {
        // 一些业务逻辑
        orderRepository.save(order);
    }
}

// 抽象层:OrderRepository接口
public interface OrderRepository {
    void save(Order order);
}

// 低层模块:MySQLRepository实现
public class MySQLRepository implements OrderRepository {
    public void save(Order order) {
        // 使用MySQL数据库保存订单
    }
}

// 另一个低层模块:PostgreSQLRepository实现
public class PostgreSQLRepository implements OrderRepository {
    public void save(Order order) {
        // 使用PostgreSQL数据库保存订单
    }
}

在应用程序中,我们可以灵活选择使用哪种具体实现,也可以把数据库的选择做成配置:

OrderRepository orderRepository = new MySQLRepository(); // 或 new PostgreSQLRepository();
OrderService orderService = new OrderService(orderRepository);

通过这种方式,OrderService变得更具重用性、可测试性更强,并且与具体的存储实现解耦,满足依赖倒置原则的要求。

方案二:引入抽象层升级版

尽管方式一也实现了依赖倒置,但是这种实现方式高层组件以及组件是封装在一个包中,对低层组件的重用会差一些,因此,另一种更灵活的解决方案是将抽象组件提取到一组独立的包/库中,如下图:

因此,上述电商示例的依赖关系会变成下图:

这种实现方式将每一层分离成自己的封装,鼓励任何层的再利用,提供稳健性和移动性。

两种方案的核心思想都是一样的,只是在灵活性和组件复用的考虑上略有差异。

依赖倒置的实例

在 Java语言中,使用依赖倒置原则的框架或者技术点有很多,这里列举2个比较较常用的例子:

1.Spring

Spring框架的核心之一是依赖注入(Dependency Injection, DI),这是依赖倒置原则的一个实现。通过Spring容器管理对象的创建和依赖关系,可以使得高层模块和低层模块都依赖于抽象。Spring支持构造器注入、setter注入和接口注入等多种方式。

2.Java SPI

Java SPI(Service Provider Interface)机制也体现了依赖倒置原则,SPI机制通过定义接口和服务提供者(Service Providers),使得高层模块(使用者)和低层模块(提供者)之间的依赖关系可以通过接口进行解耦。具体来说,高层模块依赖于抽象(接口),而不是具体的实现,从而实现了依赖倒置原则。

JDBC(Java Database Connectivity)就是使用 SPI机制来加载和注册数据库驱动程序,使得应用程序可以动态地使用不同的数据库而无需修改代码。

JDBC SPI的工作原理:

  • 定义服务接口:JDBC API定义了一组接口,如 java.sql.Driver。
  • 实现服务接口:每个数据库厂商实现这些接口,例如,MySQL的驱动实现了 java.sql.Driver接口。
  • 声明服务提供者:数据库驱动的JAR包中包含一个文件,声明实现类。
  • 加载服务提供者:通过 ServiceLoader或 JDBC API动态加载并实例化驱动实现。

总结

本文通过一个电商示例分析了什么是依赖倒置原则,并且提出了依赖倒置的两种实现风格,通过引入抽象层,可以降低系统的耦合度,提升系统的扩展性和可维护性。因此,在实际开发中,我们应当始终遵循依赖倒置原则,设计灵活、可扩展的系统架构,从而应对复杂多变的业务需求。

责任编辑:赵宁宁 来源: 猿java
相关推荐

2012-03-07 10:40:19

Java设计模式

2024-05-10 07:19:46

IOC依赖倒置控制反转

2013-09-02 17:53:41

MVC架构设计MEF

2023-11-05 15:05:15

AI论文

2012-08-20 09:35:37

DIP接口

2011-11-30 15:28:32

在线协作系统

2018-11-06 12:26:14

AI教育人工智能

2011-09-07 09:21:01

设计模式

2011-12-31 14:47:10

Web App

2021-03-08 21:44:33

以太坊区块链比特币

2021-12-15 23:42:56

Webpack原理实践

2023-10-28 00:02:55

2023-05-05 18:53:23

数据湖数据仓库

2017-02-23 07:45:33

科技新闻早报

2018-12-27 09:10:45

2013-06-20 11:11:00

程序员经理

2024-02-23 08:38:34

AI模型计算机

2022-07-29 14:25:54

Java李三红版本升级

2024-06-18 08:31:33

2012-07-27 10:01:30

前端框架前端工具
点赞
收藏

51CTO技术栈公众号