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

Что такое dependency injection?

2.0 Middle🔥 192 комментариев
#JavaScript Core#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Что такое Dependency Injection

Dependency Injection (DI) — это паттерн проектирования, который решает проблему жестких связей между компонентами. Вместо того, чтобы создавать зависимости внутри компонента, мы передаем их извне.

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

Представь компонент, который создает свои зависимости:

// Плохо: зависимость создается внутри
class UserService {
  constructor() {
    this.database = new Database(); // Создание внутри
    this.logger = new Logger(); // Создание внутри
  }
  
  getUser(id) {
    const user = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
    this.logger.log(`User fetched: ${id}`);
    return user;
  }
}

const service = new UserService();

Проблемы:

  • Сложно тестировать — нельзя подменить Database на тестовую версию
  • Жесткая связь — UserService зависит от конкретных реализаций Database и Logger
  • Сложно менять реализацию — если захочешь использовать другую БД, нужно менять UserService

Решение: Dependency Injection

Передаем зависимости извне:

// Хорошо: зависимости передаются
class UserService {
  constructor(database, logger) {
    this.database = database; // Получена как параметр
    this.logger = logger; // Получена как параметр
  }
  
  getUser(id) {
    const user = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
    this.logger.log(`User fetched: ${id}`);
    return user;
  }
}

// Использование
const realDatabase = new PostgresDatabase();
const realLogger = new ConsoleLogger();
const service = new UserService(realDatabase, realLogger);

Преимущества:

  • Легко тестировать — подменяем зависимости на тестовые
  • Слабая связь — UserService не знает о конкретных реализациях
  • Гибкость — легко менять реализации

Три способа DI

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

class PaymentService {
  constructor(stripe, notificationService) {
    this.stripe = stripe;
    this.notificationService = notificationService;
  }
  
  processPayment(amount) {
    const result = this.stripe.charge(amount);
    this.notificationService.send("Payment processed");
    return result;
  }
}

// Использование
const paymentService = new PaymentService(
  new StripeGateway(),
  new EmailNotificationService()
);

2. Property Injection (через свойства)

class ReportGenerator {
  generate() {
    return this.database.fetchData();
  }
}

const generator = new ReportGenerator();
generator.database = new MySQLDatabase(); // Установка после создания
const report = generator.generate();

3. Method Injection (через методы)

class FileProcessor {
  process(data, logger) { // logger — зависимость
    logger.info("Processing started");
    // ...
    logger.info("Processing finished");
  }
}

const processor = new FileProcessor();
processor.process(data, new ConsoleLogger());

DI в React

Context API для DI

// Создаем контекст для зависимостей
const ApiContext = React.createContext();

// Провайдер зависимостей
function App() {
  const apiService = new ApiService("https://api.example.com");
  const authService = new AuthService();
  
  return (
    <ApiContext.Provider value={{ apiService, authService }}>
      <MainContent />
    </ApiContext.Provider>
  );
}

// Компонент получает зависимости из контекста
function UserList() {
  const { apiService } = useContext(ApiContext);
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    apiService.fetchUsers().then(setUsers);
  }, [apiService]);
  
  return <div>{users.map(u => <div>{u.name}</div>)}</div>;
}

Custom Hook для DI

function useUserService() {
  const [userService] = useState(() => {
    return new UserService(
      new ApiRepository(),
      new LocalStorageCache()
    );
  });
  return userService;
}

function UserProfile({ userId }) {
  const userService = useUserService();
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    userService.getUser(userId).then(setUser);
  }, [userId, userService]);
  
  return <div>{user?.name}</div>;
}

Props Injection (самый простой способ)

function UserList({ userService }) {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    userService.fetchAll().then(setUsers);
  }, [userService]);
  
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

// Использование
function App() {
  const userService = new UserService();
  return <UserList userService={userService} />;
}

Пример: Тестирование с DI

// Сервис
class OrderService {
  constructor(paymentGateway, emailService) {
    this.paymentGateway = paymentGateway;
    this.emailService = emailService;
  }
  
  async createOrder(userId, items) {
    const amount = items.reduce((sum, item) => sum + item.price, 0);
    await this.paymentGateway.charge(userId, amount);
    await this.emailService.sendConfirmation(userId);
    return { id: "order-123", status: "confirmed" };
  }
}

// Тест
describe("OrderService", () => {
  it("should charge payment and send email", async () => {
    // Mock зависимости
    const mockPaymentGateway = {
      charge: jest.fn().mockResolvedValue(true)
    };
    const mockEmailService = {
      sendConfirmation: jest.fn().mockResolvedValue(true)
    };
    
    const service = new OrderService(
      mockPaymentGateway,
      mockEmailService
    );
    
    const result = await service.createOrder(1, [{price: 100}]);
    
    expect(mockPaymentGateway.charge).toHaveBeenCalledWith(1, 100);
    expect(mockEmailService.sendConfirmation).toHaveBeenCalledWith(1);
    expect(result.status).toBe("confirmed");
  });
});

DI контейнеры (Advanced)

Для больших приложений используют DI контейнеры:

class DIContainer {
  constructor() {
    this.services = {};
  }
  
  register(name, factory) {
    this.services[name] = factory;
  }
  
  get(name) {
    const factory = this.services[name];
    if (!factory) throw new Error(`Service ${name} not found`);
    return factory(this);
  }
}

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

container.register("database", () => new PostgresDatabase());
container.register("logger", () => new ConsoleLogger());
container.register("userService", (c) => {
  return new UserService(
    c.get("database"),
    c.get("logger")
  );
});

const userService = container.get("userService");

Принципы DI

  1. Инверсия управления — компонент не создает свои зависимости
  2. Слабая связанность — компоненты независимы друг от друга
  3. Гибкость — легко менять реализации
  4. Тестируемость — можно подменять зависимости в тестах

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

  • Всегда — для сервисов, репозиториев, утилит
  • В тестах — подменяй зависимости на mock и
  • В больших приложениях — используй DI контейнеры

Заключение

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