Dependency Injection em TypeScript: para iniciantes
\n\nNo mundo do desenvolvimento de software, o termo Dependency Injection (DI), ou injeção de dependências, é frequentemente encontrado. Para um desenvolvedor iniciante em TypeScript, isso pode parecer assustador, mas na verdade é um padrão simples e poderoso que torna seu código mais limpo, testável e fácil de manter. Neste artigo, vamos entender o que é DI, por que é necessário e como aplicá-lo na prática com TypeScript.
\n\nO que é Dependency Injection?
\n\nImagine que você está montando um computador. Em vez de embutir o processador diretamente na placa-mãe na fábrica, você deixa um slot onde pode encaixar qualquer processador compatível. Dependency Injection funciona com o mesmo princípio: em vez de a classe criar suas próprias dependências (como um banco de dados ou um cliente HTTP), ela as recebe de fora.
\n\nEm termos simples, DI é quando um objeto não cria seus "ajudantes" internamente, mas os recebe prontos através do construtor ou de um método.
\n\nProblema sem DI: Alto acoplamento
\n\nVamos considerar um exemplo comum em projetos reais:
\n\nclass UserService {\n private database: MySQLDatabase;\n\n constructor() {\n this.database = new MySQLDatabase(); // Alto acoplamento!\n }\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\nAqui, UserService está fortemente acoplado a MySQLDatabase. Se você quiser migrar para PostgreSQL ou escrever testes unitários com um banco de dados fictício, precisará reescrever toda a classe. Isso viola o princípio Open-Closed do SOLID.
Solução com DI: Flexibilidade e testabilidade
\n\nAgora, vamos reescrever a mesma classe usando DI:
\n\ninterface Database {\n findUserById(id: number): User | null;\n}\n\nclass UserService {\n constructor(private database: Database) {} // Dependência passada externamente\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\n// Uso\nconst mySQL = new MySQLDatabase();\nconst service = new UserService(mySQL); // Injetamos a dependência\n\nAgora, UserService não sabe com qual banco de dados está trabalhando. Você pode facilmente substituir MySQLDatabase por PostgreSQLDatabase ou MockDatabase para testes.
Principais formas de injeção de dependências
\n\nEm TypeScript, existem três formas principais de injetar dependências. Vamos analisar cada uma delas.
\n\n1. Injeção pelo construtor (Constructor Injection)
\n\nEsta é a forma mais comum e recomendada. As dependências são passadas através do construtor da classe.
\n\nclass Logger {\n log(message: string) {\n console.log(`[LOG]: ${message}`);\n }\n}\n\nclass OrderService {\n constructor(private logger: Logger) {}\n\n createOrder(data: any) {\n this.logger.log('Pedido criado');\n // lógica de criação do pedido\n }\n}\n\nconst logger = new Logger();\nconst orderService = new OrderService(logger);\n\n2. Injeção por propriedade (Property Injection)
\n\nA dependência é atribuída diretamente a uma propriedade do objeto após sua criação. É menos usada, pois pode levar à inicialização incompleta do objeto.
\n\nclass EmailService {\n sendEmail(to: string, message: string) {\n console.log(`Email enviado para ${to}: ${message}`);\n }\n}\n\nclass NotificationService {\n emailService!: EmailService; // Afirmação de tipo\n\n notifyUser(user: string) {\n this.emailService.sendEmail(user, 'Olá!');\n }\n}\n\nconst notification = new NotificationService();\nnotification.emailService = new EmailService(); // Definindo a propriedade\nnotification.notifyUser('user@example.com');\n\n