一文搞懂依赖倒置原则

依赖倒置原则 DIP

当我们提到依赖倒置原则(Dependency Inversion Principle,简称DIP)时,我们通常会提及两个核心概念:高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这个原则的目的是使系统更加灵活、可扩展,并降低模块之间的耦合度。

当我们提到依赖倒置原则(Dependency Inversion Principle,简称DIP)时,我们通常会提及两个核心概念:高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。这个原则的目的是使系统更加灵活、可扩展,并降低模块之间的耦合度。

DIP 的关键原则:
  1. 高层模块不应该依赖于低层模块:
    • 高层模块(比如业务逻辑)和低层模块(比如数据存储或外部服务)都应该依赖于抽象。
  2. 抽象不应该依赖于细节:
    • 抽象(接口或抽象类)定义了模块间的契约,而细节(具体实现)应该依赖于抽象。
DIP 的实践方法:
  1. 使用抽象类或接口:
    • 定义高层模块和低层模块之间的抽象接口或抽象类,以确保高层模块不直接依赖于具体的低层模块。
  2. 依赖注入(Dependency Injection):
    • 通过依赖注入,高层模块不负责创建或实例化低层模块,而是通过外部注入的方式,从而实现了依赖关系的反转。
  3. 工厂模式:
    • 使用工厂模式创建对象,工厂负责实例化具体的对象,高层模块通过工厂接口或抽象类获得对象,而不直接依赖具体实现。
示例1:

考虑一个简单的报告生成系统,其中有一个报告生成器 ReportGenerator,负责生成不同类型的报告。初步的实现可能如下:

javascript 复制代码
class PDFReport {
  generate() {
    return 'PDF report content';
  }
}

class HTMLReport {
  generate() {
    return 'HTML report content';
  }
}

class ReportGenerator {
  generatePDFReport() {
    const pdfReport = new PDFReport();
    return pdfReport.generate();
  }

  generateHTMLReport() {
    const htmlReport = new HTMLReport();
    return htmlReport.generate();
  }
}

这个实现违反了依赖倒置原则,因为高层模块 ReportGenerator 直接依赖于低层模块 PDFReportHTMLReport

应用依赖倒置原则的解决方案是引入一个抽象的报告接口或抽象类,让具体的报告实现依赖于这个抽象。

scala 复制代码
// 定义报告抽象接口
class Report {
  generate() {
    throw new Error('Method not implemented');
  }
}

// 具体的 PDF 报告实现
class PDFReport extends Report {
  generate() {
    return 'PDF report content';
  }
}

// 具体的 HTML 报告实现
class HTMLReport extends Report {
  generate() {
    return 'HTML report content';
  }
}

// 高层模块 ReportGenerator 依赖于 Report 接口
class ReportGenerator {
  generateReport(report) {
    return report.generate();
  }
}

现在,ReportGenerator 高层模块依赖于 Report 接口,而具体的报告实现依赖于抽象,符合依赖倒置原则。这种设计使得系统更加灵活,可以轻松添加新的报告类型,而不影响到 ReportGenerator 的代码。

示例2:

让我们考虑一个简单的例子,假设有一个电子邮件通知系统,负责向用户发送通知。初始的实现可能如下:

javascript 复制代码
class EmailNotifier {
  sendNotification(message) {
    // 发送电子邮件通知的具体实现
    console.log(`Sending email notification: ${message}`);
  }
}

class NotificationService {
  constructor() {
    this.notifier = new EmailNotifier(); // 高层模块直接依赖于低层模块
  }

  notifyUser(message) {
    this.notifier.sendNotification(message);
  }
}

const notificationService = new NotificationService();
notificationService.notifyUser("New message received");

在这个实现中,NotificationService 作为高层模块,直接依赖于 EmailNotifier 作为低层模块。这违反了依赖倒置原则。

为了符合依赖倒置原则,我们需要引入抽象层,让高层模块依赖于抽象,而低层模块依赖于相同的抽象。

javascript 复制代码
// 定义通知抽象接口
class Notification {
  sendNotification(message) {
    throw new Error('Method not implemented');
  }
}

// 具体的电子邮件通知实现
class EmailNotifier extends Notification {
  sendNotification(message) {
    console.log(`Sending email notification: ${message}`);
  }
}

// 高层模块 NotificationService 依赖于 Notification 接口
class NotificationService {
  constructor(notifier) {
    this.notifier = notifier;
  }

  notifyUser(message) {
    this.notifier.sendNotification(message);
  }
}

const emailNotifier = new EmailNotifier();
const notificationService = new NotificationService(emailNotifier);
notificationService.notifyUser("New message received");

现在,NotificationService 高层模块依赖于 Notification 接口,而 EmailNotifier 低层模块依赖于相同的抽象。这样的设计使得我们可以轻松地引入新的通知方式,而不需要修改 NotificationService 的代码。例如,我们可以添加一个新的 SMSNotifier 类,它也继承自 Notification,然后将其传递给 NotificationService。这种设计符合依赖倒置原则,提高了系统的灵活性和可维护性。

相关推荐
捕鲸叉3 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点4 小时前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
凉辰4 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
菜菜-plus4 小时前
java设计模式之策略模式
java·设计模式·策略模式
暗黑起源喵4 小时前
设计模式-迭代器
设计模式
lexusv8ls600h6 小时前
微服务设计模式 - 网关路由模式(Gateway Routing Pattern)
spring boot·微服务·设计模式
sniper_fandc8 小时前
抽象工厂模式
java·设计模式·抽象工厂模式
无敌岩雀11 小时前
C++设计模式结构型模式———外观模式
c++·设计模式·外观模式
hxj..12 小时前
【设计模式】观察者模式
java·观察者模式·设计模式
XYX的Blog14 小时前
设计模式09-行为型模式2(状态模式/策略模式/Java)
设计模式·状态模式·策略模式