一文搞懂依赖倒置原则

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

相关推荐
小白不太白95023 分钟前
设计模式之 观察者模式
观察者模式·设计模式
小白不太白9502 小时前
设计模式之 责任链模式
python·设计模式·责任链模式
吾与谁归in2 小时前
【C#设计模式(13)——代理模式(Proxy Pattern)】
设计模式·c#·代理模式
吾与谁归in2 小时前
【C#设计模式(14)——责任链模式( Chain-of-responsibility Pattern)】
设计模式·c#·责任链模式
闲人一枚(学习中)2 小时前
设计模式-创建型-原型模式
设计模式
Iced_Sheep3 小时前
干掉 if else 之策略模式
后端·设计模式
哪 吒10 小时前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_102210 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神13 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术14 小时前
23种设计模式速记法
设计模式