← Назад к вопросам

Что такое внедрение зависимостей?

1.7 Middle🔥 141 комментариев
#Архитектура и паттерны#ООП

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Dependency Injection (DI) — это паттерн проектирования, при котором объект получает свои зависимости извне, вместо того чтобы создавать их самостоятельно. Это один из ключевых принципов чистой архитектуры и SOLID (D — Dependency Inversion).

Проблема без DI

// Плохо: класс создаёт свои зависимости
class UserService {
  constructor() {
    this.database = new PostgresDatabase(); // Жёсткая связь
    this.emailService = new GmailService();  // Жёсткая связь
  }
  
  async register(email, password) {
    this.database.save(email, password);
    this.emailService.send(email, 'Welcome!');
  }
}

// Проблемы:
// 1. Невозможно протестировать (нельзя подменить database)
// 2. Сложно менять реализацию (нужно менять класс)
// 3. Если DatabaseService изменится, нужна переписка UserService

Решение: Dependency Injection

// Хорошо: зависимости передаются в constructor
class UserService {
  constructor(database, emailService) {
    this.database = database;
    this.emailService = emailService;
  }
  
  async register(email, password) {
    this.database.save(email, password);
    this.emailService.send(email, 'Welcome!');
  }
}

// Использование
const db = new PostgresDatabase();
const email = new GmailService();
const userService = new UserService(db, email);

await userService.register('user@example.com', 'password123');

Три типа DI

1. Constructor Injection (конструктор)

class UserService {
  constructor(database) {
    this.database = database;
  }
  
  async getUser(id) {
    return this.database.findById(id);
  }
}

const db = new Database();
const service = new UserService(db);

Плюсы:

  • Все зависимости видны в конструкторе
  • Объект полностью инициализирован при создании
  • Легко тестировать

Минусы:

  • Конструктор может стать очень большим

2. Setter Injection

class UserService {
  setDatabase(database) {
    this.database = database;
  }
  
  async getUser(id) {
    return this.database.findById(id);
  }
}

const service = new UserService();
service.setDatabase(new Database());

Минусы:

  • Объект может быть не полностью инициализирован
  • Зависимости не очевидны

3. Interface Injection

interface Injectable {
  inject(dependencies: object): void;
}

class UserService implements Injectable {
  inject(dependencies) {
    this.database = dependencies.database;
    this.emailService = dependencies.emailService;
  }
}

IoC Container (Inversion of Control)

Проблема: если DI повсеместно, получим спагетти-код из создания объектов.

Решение: использовать IoC контейнер, который управляет жизненным циклом объектов.

// Простой IoC контейнер
class Container {
  constructor() {
    this.services = new Map();
  }
  
  register(name, factory) {
    this.services.set(name, factory);
  }
  
  resolve(name) {
    const factory = this.services.get(name);
    if (!factory) throw new Error(`Service ${name} not found`);
    return factory(this);
  }
}

// Использование
const container = new Container();

container.register('database', () => new PostgresDatabase());
container.register('emailService', () => new GmailService());

container.register('userService', (c) => {
  return new UserService(
    c.resolve('database'),
    c.resolve('emailService')
  );
});

const userService = container.resolve('userService');

Express + DI пример

class Database {
  async query(sql) {
    // реальный запрос
  }
}

class UserRepository {
  constructor(database) {
    this.database = database;
  }
  
  async findById(id) {
    return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

class UserService {
  constructor(repository) {
    this.repository = repository;
  }
  
  async getUser(id) {
    return this.repository.findById(id);
  }
}

class UserController {
  constructor(userService) {
    this.userService = userService;
  }
  
  async handleGetUser(req, res) {
    const user = await this.userService.getUser(req.params.id);
    res.json(user);
  }
}

// IoC контейнер
const container = new Container();
container.register('database', () => new Database());
container.register('userRepository', (c) => new UserRepository(c.resolve('database')));
container.register('userService', (c) => new UserService(c.resolve('userRepository')));
container.register('userController', (c) => new UserController(c.resolve('userService')));

// Express
app.get('/users/:id', (req, res) => {
  const controller = container.resolve('userController');
  controller.handleGetUser(req, res);
});

Тестирование с DI

// Без DI: невозможно тестировать
class UserService {
  constructor() {
    this.database = new RealDatabase(); // Жёсткая связь
  }
}

// С DI: легко подменить
class MockDatabase {
  async findById(id) {
    return { id, name: 'John' };
  }
}

test('UserService.getUser', async () => {
  const mockDb = new MockDatabase();
  const userService = new UserService(mockDb);
  
  const user = await userService.getUser(1);
  
  expect(user.name).toBe('John');
});

Frameworks с встроенным DI

NestJS (рекомендуется для Node.js)

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor(private database: Database) {}
  
  async getUser(id) {
    return this.database.findById(id);
  }
}

NestJS автоматически управляет DI через декораторы.

Awilix (лёгкий IoC контейнер)

const { createContainer, asClass, asValue } = require('awilix');

const container = createContainer();

container.register({
  database: asClass(Database).singleton(),
  userService: asClass(UserService).singleton(),
  userController: asClass(UserController).singleton(),
});

const userController = container.resolve('userController');

SOLID принципы DI

D — Dependency Inversion Principle:

// Плохо: зависит от конкретной реализации
class UserService {
  constructor() {
    this.payment = new StripePayment(); // Жёсткая связь
  }
}

// Хорошо: зависит от интерфейса
class UserService {
  constructor(paymentProvider) { // Может быть Stripe, PayPal, etc.
    this.payment = paymentProvider;
  }
}

Когда использовать DI

  • Всегда в production коде
  • Для тестируемости
  • Для гибкости архитектуры
  • Для масштабируемости

Когда не использовать

  • Простые скрипты
  • Прототипы
  • Код с одной вспомогательной библиотекой

Dependency Injection — это мощный паттерн, который делает код чище, тестируемее и более гибким. В современном Node.js разработка без DI немыслима для production приложений.