Injeção de Dependência em TypeScript: guia prático com exemplos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Injeção de Dependência em TypeScript: da teoria à prática



Injeção de Dependência (DI) é um dos principais padrões de projeto que permite criar aplicações flexíveis, testáveis e de fácil manutenção. Em TypeScript, graças à tipagem forte e aos recursos de decoradores, a implementação de DI torna-se especialmente elegante. Neste artigo, vamos analisar como implementar a Injeção de Dependência na prática, usando TypeScript, e ver por que esse padrão é a pedra angular do desenvolvimento moderno.



O que é Injeção de Dependência e por que ela é necessária?



Injeção de Dependência (DI) é um padrão no qual um objeto recebe suas dependências externamente, em vez de criá-las por conta própria. Em vez de escrever new SomeService() dentro de uma classe, passamos uma instância pronta através do construtor ou método.



Principais vantagens da DI:

  • Redução do acoplamento (Coupling) — as classes não dependem de implementações específicas, mas apenas de abstrações (interfaces).
  • Facilitação dos testes — podemos substituir facilmente serviços reais por mocks ou stubs.
  • Aumento da reutilização — as dependências podem ser substituídas sem alterar o código do consumidor.
  • Conformidade com o princípio da inversão de dependência (DIP do SOLID) — módulos de alto nível não dependem de módulos de baixo nível.


Implementação de Injeção de Dependência em TypeScript sem frameworks



Vamos começar com um exemplo simples. Suponha que temos um serviço de logging e uma classe que o utiliza.



// Sem DI - acoplamento rígidoclass Logger {    log(message: string): void {        console.log(`[LOG]: ${message}`);    }}

class UserService { private logger: Logger;

constructor() { this.logger = new Logger(); // Problema: dependência rígida }

createUser(name: string): void { this.logger.log(`Usuário criado: ${name}`); // ... lógica de criação }}

// Com DI - através do construtorinterface ILogger { log(message: string): void;}

class ConsoleLogger implements ILogger { log(message: string): void { console.log(`[CONSOLE LOG]: ${message}`); }}

class FileLogger implements ILogger { log(message: string): void { // Lógica de escrita em arquivo console.log(`[FILE LOG]: ${message}`); }}

class UserServiceWithDI { constructor(private logger: ILogger) {} // Dependência injetada externamente

createUser(name: string): void { this.logger.log(`Usuário criado: ${name}`); }}

// Usoconst consoleLogger = new ConsoleLogger();const userService = new UserServiceWithDI(consoleLogger);userService.createUser('Alice'); // [CONSOLE LOG]: Usuário criado: Alice


Como você pode ver, agora UserServiceWithDI não sabe qual logger está sendo usado. Podemos facilmente substituí-lo por FileLogger ou um mock para testes.



DI Avançado: criando um contêiner IoC simples



Quando há muitas dependências, a injeção manual através do construtor se torna complicada. É aí que entra o contêiner IoC (Inversion of Control Container). Ele gerencia a criação e o ciclo de vida dos objetos.



Vamos implementar um contêiner simples em TypeScript:



// Tipo para fábrica de dependênciastype Factory<T> = (container: Container) => T;

class Container { private factories = new Map<string, Factory<any>>(); private instances = new Map<string, any>();

// Registro de dependência (fábrica é chamada a cada solicitação) register<T>(name: string, factory: Factory<T>): void { this.factories.set(name, factory); }

// Registro de singleton (fábrica é chamada uma vez) singleton<T>(name: string, factory: Factory<T&g

Blogs

Book Recommendations