一文搞懂依赖倒置原则

依赖倒置原则 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。这种设计符合依赖倒置原则,提高了系统的灵活性和可维护性。

相关推荐
蜡笔小新..11 小时前
【设计模式】软件设计原则——开闭原则&里氏替换&单一职责
java·设计模式·开闭原则·单一职责原则
性感博主在线瞎搞12 小时前
【面向对象】设计模式概念和分类
设计模式·面向对象·中级软件设计师·设计方法
lucifer31112 小时前
JavaScript 中的组合模式(十)
javascript·设计模式
lucifer31112 小时前
JavaScript 中的装饰器模式(十一)
javascript·设计模式
蜡笔小新..13 小时前
【设计模式】软件设计原则——依赖倒置&合成复用
设计模式·依赖倒置原则·合成复用原则
刷帅耍帅13 小时前
设计模式-代理模式
设计模式·代理模式
神的孩子都在歌唱21 小时前
行为设计模式 -观察者模式- JAVA
java·观察者模式·设计模式
刷帅耍帅1 天前
设计模式-解释器模式
设计模式·解释器模式
刷帅耍帅1 天前
设计模式-备忘录模式
设计模式·备忘录模式