Princípios SOLID em TypeScript: teoria e prática do A ao Z
\n\nSOLID é um acrônimo de cinco princípios fundamentais da programação orientada a objetos, formulados por Robert Martin (Tio Bob). Esses princípios ajudam a criar código flexível, sustentável e escalável. Neste artigo, vamos detalhar cada princípio SOLID com exemplos em TypeScript, desde definições simples até cenários reais de uso.
\n\n1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
\n\nDefinição: Cada classe deve ter apenas um motivo para mudar. Em outras palavras, a classe deve ser responsável por uma tarefa claramente definida.
\n\nProblema: Quando uma classe assume várias responsabilidades, torna-se difícil testá-la, estendê-la e mantê-la. A alteração de uma função pode quebrar outra.
\n\nExemplo de violação do SRP:
\n// ❌ Violação do SRP: a classe lida com armazenamento, formatação e envio\nclass UserService {\n saveUser(user: User): void {\n // Salvando no banco de dados\n console.log('User saved');\n }\n\n formatUserData(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n\n sendWelcomeEmail(user: User): void {\n // Enviando email\n console.log('Email sent');\n }\n}\n\nSolução com SRP:
\n// ✅ Cada classe é responsável por sua tarefa\nclass UserRepository {\n save(user: User): void {\n console.log('User saved');\n }\n}\n\nclass UserFormatter {\n format(user: User): string {\n return `Name: ${user.name}, Email: ${user.email}`;\n }\n}\n\nclass EmailService {\n sendWelcome(user: User): void {\n console.log('Email sent');\n }\n}\n\n2. Open-Closed Principle (OCP) — Princípio Aberto/Fechado
\n\nDefinição: As classes devem estar abertas para extensão, mas fechadas para modificação. Isso significa que você deve ser capaz de adicionar novas funcionalidades sem alterar o código existente.
\n\nExemplo de violação do OCP:
\n// ❌ Ao adicionar um novo tipo de forma, será necessário alterar o método calculateArea\nclass AreaCalculator {\n calculateArea(shape: any): number {\n if (shape.type === 'circle') {\n return Math.PI * shape.radius ** 2;\n } else if (shape.type === 'rectangle') {\n return shape.width * shape.height;\n }\n throw new Error('Unknown shape');\n }\n}\n\nSolução com OCP (usando polimorfismo):
\n// ✅ Usando interface e herança\ninterface Shape {\n area(): number;\n}\n\nclass Circle implements Shape {\n constructor(private radius: number) {}\n area(): number {\n return Math.PI * this.radius ** 2;\n }\n}\n\nclass Rectangle implements Shape {\n constructor(private width: number, private height: number) {}\n area(): number {\n return this.width * this.height;\n }\n}\n\nclass AreaCalculator {\n calculateArea(shape: Shape): number {\n return shape.area(); // Não requer alterações ao adicionar novas formas\n }\n}\n\n3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Liskov
\n\nDefinição: Os objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção do programa. A classe filha não deve quebrar o contrato da classe pai.
\n\nExemplo de violação do LSP:
\n// ❌ Violação: a subclasse altera o comportamento da superclasse\nclass Bird {\n fly(): void {\n console.log('Flying');\n }\n}\n\nclass Penguin extends Bird {\n fly(): void {\n throw new Error('Penguins cannot fly!'); // Quebra do contrato\n }\n}\n\nSolução com LSP:
\n// ✅ Separação