依赖倒置原则详解

目录

概述     

高层次模块不应该依赖于低层次模块:

抽象不应该依赖于细节:

意图

代码 示例

步骤 1: 定义抽象层

步骤 2: 实现具体细节

ConsoleLogger

FileLogger

步骤 3: 创建使用抽象的模块

步骤 4: 使用具体实现

原理解析

优点



概述     

    依赖倒置原则(Dependency Inversion Principle, DIP)是罗伯特·C·马丁(Robert C. Martin)提出的一个面向对象设计原则。主要强调的是高层次模块不应该依赖于低层次模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则可以分为两个部分来理解:

  1. 高层次模块不应该依赖于低层次模块

    • 高层次模块通常是系统中更接近业务逻辑的部分,而低层次模块则是实现具体功能的细节。
    • 为了保持高层模块的灵活性和可维护性,它们应当依赖于抽象接口或类,而不是具体的实现细节。
  2. 抽象不应该依赖于细节

    • 抽象是指定义好的接口或者基类,它们描述了行为的一般形式。
    • 细节指的是具体的实现,这些实现应该依赖于抽象提供的接口或基类来进行定义。

遵循依赖倒置原则可以带来以下好处:

  • 降低耦合度:通过依赖于抽象而非具体实现,可以减少模块间的耦合程度。
  • 提高系统的灵活性:当需要改变某个模块的具体实现时,只要新的实现仍然符合原有的抽象接口,就不需要修改使用该模块的其他部分。
  • 易于扩展:增加新的实现方式只需实现已有的抽象接口,不需要改动现有代码。

一个典型的实现方法是使用接口或抽象类作为依赖的基础,然后让具体实现去继承或实现这些抽象。这样,当需要更改或扩展功能时,只需要添加新的实现而无需修改现有的高层模块。

意图

     面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。

    面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度

代码 示例

步骤 1: 定义抽象层

定义一个抽象层——Logger接口:

// Logger.java
public interface Logger {
    void log(String message);
}

步骤 2: 实现具体细节

Logger接口创建具体的实现类。这里有两个实现:ConsoleLoggerFileLogger

ConsoleLogger
// ConsoleLogger.java
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Console Logger: " + message);
    }
}
FileLogger
// FileLogger.java
import java.io.FileWriter;
import java.io.IOException;

public class FileLogger implements Logger {
    private String fileName = "log.txt";

    @Override
    public void log(String message) {
        try (FileWriter writer = new FileWriter(fileName, true)) {
            writer.write("File Logger: " + message + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

步骤 3: 创建使用抽象的模块

创建一个UserService类,使用Logger接口进行日志记录。

// UserService.java
public class UserService {
    private final Logger logger;

    // 通过构造函数注入Logger实例
    public UserService(Logger logger) {
        this.logger = logger;
    }

    public void createUser(String username) {
        // 模拟创建用户的过程
        logger.log("Creating user: " + username);
        System.out.println("User created: " + username);
    }
}

步骤 4: 使用具体实现

在主程序中使用UserService类,并传递不同的日志实现。

// Main.java
public class Main {
    public static void main(String[] args) {
        // 使用ConsoleLogger
        Logger consoleLogger = new ConsoleLogger();
        UserService userServiceWithConsoleLogger = new UserService(consoleLogger);
        userServiceWithConsoleLogger.createUser("Alice");

        // 使用FileLogger
        Logger fileLogger = new FileLogger();
        UserService userServiceWithFileLogger = new UserService(fileLogger);
        userServiceWithFileLogger.createUser("Bob");
    }
}

原理解析

在这个例子中,遵循依赖倒置原则的两个方面:

  1. 高层次模块不依赖于低层次模块

    • UserService类是一个高层次模块,依赖于抽象接口Logger
    • ConsoleLoggerFileLogger是具体的实现细节,实现Logger接口。
  2. 抽象不依赖于细节

    • Logger接口定义日志记录的行为,没有涉及到任何具体的实现细节。
    • 具体的日志记录器(ConsoleLoggerFileLogger)依赖于Logger接口并实现了它的方法。

优点

  • 解耦:通过依赖于抽象,可以轻松地更换或添加新的日志记录器实现,而不需要修改UserService类。
  • 易于测试:由于UserService类依赖于抽象可以在单元测试中使用模拟(mocks)或存根(stubs)来代替实际的日志记录器。
  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何遇mirror

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值