Injeção de Dependência em JavaScript: Guia Completo
Injeção de Dependência (DI) é um poderoso padrão de projeto que está na base do princípio Inversão de Controle (IoC). Em vez de um objeto criar suas próprias dependências, a DI as "injeta" de fora. Isso torna o código mais flexível, testável e sustentável. Neste artigo, vamos analisar como implementar a Injeção de Dependência em JavaScript e Node.js, usando exemplos simples e claros.
O que é Injeção de Dependência e por que ela é necessária?
Imagine que você tem uma classe UserService que trabalha com um banco de dados. Sem DI, você provavelmente criaria uma instância do banco de dados diretamente dentro do serviço:
class UserService { constructor() { this.db = new Database(); // Acoplamento rígido }
getUser(id) { return this.db.findUser(id); }}Este código viola o Princípio da Inversão de Dependência (Dependency Inversion Principle). Ao testar o UserService, você não conseguirá substituir o banco de dados real por um objeto mock sem alterar o código da classe. A DI resolve este problema:
class UserService { constructor(database) { this.db = database; // A dependência é passada de fora }
getUser(id) { return this.db.findUser(id); }}
// Usoconst db = new Database();const userService = new UserService(db);Principais vantagens da DI:
- Testabilidade: Fácil substituir dependências por mocks ou stubs.
- Baixo acoplamento: As classes dependem de abstrações (interfaces), não de implementações concretas.
- Flexibilidade: É possível alterar facilmente o comportamento do sistema substituindo implementações.
- Reutilização de código: Os componentes tornam-se independentes e podem ser usados em diferentes contextos.
Formas de injeção de dependências em JavaScript
Em JavaScript, existem três formas principais de injetar dependências:
1. Injeção via construtor (Constructor Injection)
A forma mais comum e recomendada. As dependências são passadas no momento da criação do objeto.
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class OrderService { constructor(logger) { this.logger = logger; }
createOrder(data) { this.logger.log('Novo pedido criado'); // Lógica de criação do pedido }}
const logger = new Logger();const orderService = new OrderService(logger);orderService.createOrder({ product: 'Laptop', price: 1500 });2. Injeção via setters (Setter Injection)
As dependências são definidas após a criação do objeto através de métodos setters. Adequado para dependências opcionais.
class EmailService { constructor() { this.transporter = null; }
setTransporter(transporter) { this.transporter = transporter; }
send(email) { if (!this.transporter) { throw new Error('Transporter não configurado'); } this.transporter.sendMail(email); }}
const emailService = new EmailService();emailService.setTransporter(someMailTransporter);3. Injeção via interface (Interface Injection — condicionalmente)
Em JavaScript não há interfaces nativas, mas é possível usar TypeScript ou documentação para descrever contratos. Na prática, isso se resume a verificar a existência de métodos.
Criando um contêiner DI simples em Node.js
Para gerenciar dependências em aplicações grandes, é conveniente usar um contêiner DI. Ele armazena todas as dependências e as resolve automaticamente. Aqui está uma implementação mínima:
class Container { constructor() { this.services = new Map(); this.singletons = new Map(); }
// Registro de fábrica para o serviço register(name,