Приведи пример принципа Dependency Inversion

«Приведи пример принципа Dependency Inversion» — вопрос из категории ООП, который задают на 35% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Принцип инверсии зависимостей (DIP) гласит, что модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. В Node.js это часто реализуется через внедрение зависимостей (Dependency Injection).

Проблема (нарушение DIP):

// Модуль верхнего уровня жестко зависит от конкретной реализации
const AWS = require('aws-sdk');

class ReportService {
  constructor() {
    // Прямое создание зависимости
    this.s3 = new AWS.S3();
  }

  async saveReport(data) {
    await this.s3.upload({ Bucket: 'my-bucket', Key: 'report.json', Body: data }).promise();
  }
}
// Тестировать или изменить хранилище сложно.

Решение (соблюдение DIP):

// 1. Абстракция (интерфейс/контракт)
class StorageProvider {
  async save(fileName, data) {
    throw new Error('save() method not implemented');
  }
}

// 2. Конкретные реализации зависят от абстракции
class S3Storage extends StorageProvider {
  constructor(s3Client) {
    super();
    this.client = s3Client;
  }
  async save(fileName, data) {
    const params = { Bucket: process.env.BUCKET_NAME, Key: fileName, Body: data };
    return this.client.upload(params).promise();
  }
}

class FileSystemStorage extends StorageProvider {
  constructor(fsModule) {
    super();
    this.fs = fsModule.promises;
  }
  async save(fileName, data) {
    return this.fs.writeFile(`./uploads/${fileName}`, data);
  }
}

// 3. Модуль верхнего уровня зависит от абстракции
class ReportService {
  // Зависимость внедряется извне
  constructor(storageProvider) {
    this.storage = storageProvider;
  }

  async generateAndSave() {
    const reportData = JSON.stringify({ report: 'data' });
    // Используется абстракция, а не конкретный S3 или FS
    await this.storage.save('report.json', reportData);
  }
}

// 4. Компоновка зависимостей (например, в корне приложения или контейнере DI)
const { S3 } = require('aws-sdk');
const s3Client = new S3();
const storage = new S3Storage(s3Client); // Или new FileSystemStorage(require('fs'))
const reportService = new ReportService(storage);

// Теперь ReportService не знает и не зависит от деталей хранения.
// Легко тестировать с моком StorageProvider и менять реализацию.