Introdução: Por que SOLID é importante em TypeScript
SOLID é um acrônimo de cinco princípios fundamentais de design orientado a objetos, formulados por Robert Martin (Uncle Bob). No contexto do TypeScript, esses princípios adquirem valor especial devido ao sistema de tipos estrito e ao suporte a paradigmas modernos de OOP. A aplicação do SOLID torna o código mais flexível, testável e sustentável, o que é criticamente importante para projetos escaláveis.
Neste artigo, vamos analisar cada princípio SOLID com exemplos práticos em TypeScript, mostrar antipadrões e suas implementações corretas. Você aprenderá como escrever código que não quebra ao adicionar novas funcionalidades.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Teoria
Cada classe deve ter apenas um motivo para ser alterada. Isso significa que a classe deve ser responsável por apenas uma parte da funcionalidade do programa. Se uma classe executa várias tarefas, é mais difícil de manter e testar.
Antipadrão (violação do SRP)
class UserService { saveUser(user: User): void { // Salvar no BD console.log('User saved'); }
sendEmail(user: User, message: string): void { // Enviar email console.log(`Email sent to ${user.email}`); }
generateReport(user: User): string { // Gerar relatório return `Report for ${user.name}`; }}Implementação correta (SRP)
class UserRepository { save(user: User): void { // Apenas salvar }}
class EmailService { send(user: User, message: string): void { // Apenas enviar email }}
class ReportGenerator { generate(user: User): string { // Apenas gerar relatórios }}2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
Teoria
As classes devem estar abertas para extensão, mas fechadas para modificação. Novas funcionalidades devem ser adicionadas através de herança ou composição, e não alterando o código existente.
Antipadrão (violação do OCP)
class PaymentProcessor { processPayment(type: string, amount: number): void { if (type === 'card') { // Processar cartão } else if (type === 'paypal') { // Processar PayPal } else if (type === 'crypto') { // Processar criptomoeda } // Ao adicionar um novo tipo — é necessário alterar a classe! }}Implementação correta (OCP)
interface PaymentMethod { process(amount: number): void;}
class CardPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing card payment: $${amount}`); }}
class PayPalPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing PayPal payment: $${amount}`); }}
class PaymentProcessor { constructor(private paymentMethod: PaymentMethod) {}
process(amount: number): void { this.paymentMethod.process(amount); }}3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Barbara Liskov
Teoria
Objetos da classe base devem ser substituíveis por objetos de suas subclasses sem quebrar o funcionamento do programa. As subclasses não devem alterar o comportamento esperado da classe base.
Antipadrão (violação do LSP)
class Rectangle { constructor(public width: number, public height: number) {}
setWidth(width: number): void { this.width = width; }
setHeight(height: number): void { this.height = height; }
getArea(): number { return this.width * this.height; }}
class Square extends Rectangle {