12 设计原则SOLID之 :D 依赖反转原则

写代码太菜了,被女朋友我分手第一天,来学习 依赖反转原则,如果你还有女朋友,那你呀好好珍惜,如果你没有,哈哈哈哈哈,那一定是代码写的太烂了!

1.什么是依赖反转原则?

1. 定义

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

上面这句话有点抽象,首先通过例子,先有点印象,再去进一步解读什么是依赖反转。 假设有一个报告生成的场景,我们有两个类:Report 用于生成报告,和 Printer 用于将报告打印出来。一开始的设计可能如下:

js 复制代码
class Report {
    public String generate() {
        return "This is a report.";
    }
}

class ReportClient {
    private Report report;

    public ReportClient() {
        this.report = new Report();  //组合
    }

    public void generateAndPrintReport() {
        String content = report.generate();
        System.out.println(content);
    }
}

在这个设计中,ReportClient 高层模块直接依赖于 Report 低层模块。如果需要修改或添加不同类型的报告,就需要修改 ReportClient,违反了开闭原则。

修改之后:

java 复制代码
// 面向接口编程
interface IReport {
    String generate();
}

// 具体实现
class Report implements IReport {
    public String generate() {
        return "This is a report.";
    }
}

// 依赖反转原则
class ReportClient {
    private IReport report;

    public ReportClient(IReport report) {
        this.report = report;
    }

    public void generateAndPrintReport() {
        String content = report.generate();
        System.out.println(content);
    }
}

现在,ReportClient 依赖于抽象接口 IReport,而不是具体的 Report 类。这样,我们可以轻松地引入新的报告类型,而不影响 ReportClient 的代码。

现在再来理解依赖反转原则要求

  1. 高层模块: 这是应用的主要逻辑,它应该依赖于抽象,而不是具体的实现细节(接口,抽象类)。
  2. 抽象: 这是一个接口或者抽象类,它定义了高层模块所需的行为(不变的,固定的)。
  3. 低层模块: 这是实际执行行为的类,它应该依赖于相同的抽象(多变的,可拔插)。

其核心思想是:要面向接口编程(OOD),不要面向实现编程。看似在讲依赖反转,实际在讲面向接口编程,哈哈哈哈哈。

上面的例子用到了抽象也用到了多态,前面学习的抽象和多态的关系都忘了,在这里复习下:

  • 焦点不同: 抽象关注的是对象的本质特征和行为的抽取,而多态关注的是同一操作在不同对象上的不同行为。
  • 实现方式不同: 抽象通过抽象类和接口来定义规范,而多态通继承和接口实现,使得同一操作可以作用在不同的对象上,产生不同的结果。
  • 应用层次不同: 抽象是在设计层次上进行的,用于建模和设计系统的结构;而多态则是在实现和运行阶段体现的,用于提高代码的灵活性和可维护性。

如果只看依赖反转,拿到这里就行了,如果想了解控制反转、依赖反转、依赖注入,那可以看下下面。

2.拓展:控制反转、依赖反转、依赖注入的关系

1. 控制反转(Inversion of Control,IoC):

  • 概念: IoC是一种软件设计思想,它反转了传统的程序设计中控制流的方向。在传统的程序设计中,程序员编写主体部分并控制其中的逻辑流程,而在IoC中,控制流程由框架或容器负责,程序员只需提供相应的扩展点或插件。
  • 实现: IoC可以通过使用设计模式(例如模板模式、策略模式)、依赖注入等方式来实现。Spring框架是一个典型的IoC容器,它通过反射和配置文件等机制实现了IoC。

以下是一个简单的示例,假设有一个简单的Java类 UserService,它负责用户的业务逻辑:

js 复制代码
public class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username, String email) {
        // 业务逻辑,例如创建用户并保存到数据库
        User user = new User(username, email);
        userRepository.save(user);
    }
}

在传统的方式中,你需要手动创建 UserService 类的实例,并注入它所依赖的 UserRepository

js 复制代码
public class MyApp {
    public static void main(String[] args) {
        UserRepository userRepository = new UserRepositoryImpl();
        UserService userService = new UserService(userRepository);
        
        // 使用 userService 进行业务操作
        userService.createUser("john_doe", "john@example.com");
    }
}

而在IoC框架中,例如Spring框架,你只需配置好依赖关系,框架负责创建和管理对象,如下所示:

js 复制代码
public class MyApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean(UserService.class);
        
        // 使用 userService 进行业务操作
        userService.createUser("john_doe", "john@example.com");
    }
}

2. 依赖反转原则(Dependency Inversion Principle,DIP):

  • 概念: DIP是SOLID原则中的一项,它强调高层模块不应该依赖于低层模块,两者都应该依赖于抽象。这是为了减小系统中模块之间的耦合度。
  • 实现: DIP可以通过使用接口、抽象类等方式实现,目的是将高层模块和低层模块的依赖关系反转,使得高层模块依赖于抽象,而不是具体实现。依赖注入是一种常见的实现方式。

3. 依赖注入(Dependency Injection,DI):

  • 概念: DI是一种实现依赖反转的具体技术,它通过将依赖关系从高层模块注入到低层模块中,以达到减小模块之间耦合度的目的。通常有三种形式的依赖注入:构造函数注入、属性注入和方法注入。
  • 实现: DI可以手动实现,也可以通过使用框架(如Spring)来自动进行依赖注入。框架负责在程序运行时将依赖关系注入到相应的类中。
  • 不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

举个例子: 假设有一个 UserService 类,它依赖于一个 UserRepository 接口来执行用户相关的数据库操作:

js 复制代码
public class UserService {
    private UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void createUser(String username, String email) {
        // 业务逻辑,例如创建用户并保存到数据库
        User user = new User(username, email);
        userRepository.save(user);
    }
}

在这个例子中,UserService 通过构造函数接收一个 UserRepository 的实例。这就是依赖注入的一种形式。

1. 构造函数注入:

js 复制代码
public class MyApp {
    public static void main(String[] args) {
        UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
        UserService userService = new UserService(userRepository);
        
        // 使用 userService 进行业务操作
        userService.createUser("john_doe", "john@example.com");
    }
}

在这个例子中,通过构造函数将 UserRepository 注入到 UserService 中。

2. Setter 方法注入:

js 复制代码
public class MyApp {
    public static void main(String[] args) {
        UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
        UserService userService = new UserService();
        userService.setUserRepository(userRepository); // 使用 setter 方法进行注入
        
        // 使用 userService 进行业务操作
        userService.createUser("john_doe", "john@example.com");
    }
}

3. 接口注入:

js 复制代码
public class MyApp {
    public static void main(String[] args) {
        UserRepository userRepository = new UserRepositoryImpl(); // 具体实现
        UserService userService = new UserService();
        userService.injectUserRepository(userRepository); // 使用接口注入
        
        // 使用 userService 进行业务操作
        userService.createUser("john_doe", "john@example.com");
    }
}

这些例子展示了依赖注入的不同形式,它们都达到了将依赖关系从 UserService 中解耦的目的。依赖注入使得代码更加灵活、可测试和可维护,因为依赖关系不再硬编码在类内部,而是由外部提供。

关系:

  • IoC是一种思想或概念,它强调控制流的反转,让框架或容器负责程序的控制流程。
  • DIP是一项原则,它强调高层模块和低层模块之间的依赖关系应该通过抽象来进行,减小耦合。
  • DI是一种实现IoC和DIP的具体技术,它通过将依赖关系注入到类中,实现了控制反转和依赖反转。
相关推荐
卡尔特斯1 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源1 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole1 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫2 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide2 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261352 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源2 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱3 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群3 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心3 小时前
从零开始学Flink:数据源
java·大数据·后端·flink